Exemple #1
0
        public virtual void test_currencyExposureBetweenFixingAndPayment()
        {
            double    eps           = 1.0e-14;
            LocalDate valuationDate = date(2014, 6, 30);
            LocalDate paymentDate   = date(2014, 7, 1);
            LocalDate fixingDate    = date(2014, 6, 27);
            FxResetNotionalExchange   resetNotionalUSD = FxResetNotionalExchange.of(CurrencyAmount.of(USD, NOTIONAL), paymentDate, FxIndexObservation.of(GBP_USD_WM, fixingDate, REF_DATA));
            FxResetNotionalExchange   resetNotionalGBP = FxResetNotionalExchange.of(CurrencyAmount.of(GBP, -NOTIONAL), paymentDate, FxIndexObservation.of(GBP_USD_WM, fixingDate, REF_DATA));
            LocalDateDoubleTimeSeries ts   = LocalDateDoubleTimeSeries.of(LocalDate.of(2014, 6, 27), 1.65);
            ImmutableRatesProvider    prov = ImmutableRatesProvider.builder(valuationDate).fxRateProvider(FX_MATRIX).discountCurve(GBP, DISCOUNT_CURVE_GBP).discountCurve(USD, DISCOUNT_CURVE_USD).timeSeries(FxIndices.GBP_USD_WM, ts).build();
            DiscountingFxResetNotionalExchangePricer test = new DiscountingFxResetNotionalExchangePricer();
            // USD
            MultiCurrencyAmount computedUSD = test.currencyExposure(resetNotionalUSD, prov);
            PointSensitivities  pointUSD    = test.presentValueSensitivity(resetNotionalUSD, prov).build();
            MultiCurrencyAmount expectedUSD = prov.currencyExposure(pointUSD.convertedTo(USD, prov)).plus(CurrencyAmount.of(resetNotionalUSD.Currency, test.presentValue(resetNotionalUSD, prov)));

            assertFalse(computedUSD.contains(USD));     // 0 USD
            assertEquals(computedUSD.getAmount(GBP).Amount, expectedUSD.getAmount(GBP).Amount, eps * NOTIONAL);
            // GBP
            MultiCurrencyAmount computedGBP = test.currencyExposure(resetNotionalGBP, prov);
            PointSensitivities  pointGBP    = test.presentValueSensitivity(resetNotionalGBP, prov).build();
            MultiCurrencyAmount expectedGBP = prov.currencyExposure(pointGBP.convertedTo(GBP, prov)).plus(CurrencyAmount.of(resetNotionalGBP.Currency, test.presentValue(resetNotionalGBP, prov)));

            assertFalse(computedGBP.contains(GBP));     // 0 GBP
            assertEquals(computedGBP.getAmount(USD).Amount, expectedGBP.getAmount(USD).Amount, eps * NOTIONAL);
            // FD approximation
            FxMatrix fxMatrixUp           = FxMatrix.of(GBP, USD, FX_RATE + EPS_FD);
            ImmutableRatesProvider provUp = ImmutableRatesProvider.builder(valuationDate).fxRateProvider(fxMatrixUp).discountCurve(GBP, DISCOUNT_CURVE_GBP).discountCurve(USD, DISCOUNT_CURVE_USD).timeSeries(FxIndices.GBP_USD_WM, ts).build();
            double expectedFdUSD          = -(test.presentValue(resetNotionalUSD, provUp) - test.presentValue(resetNotionalUSD, prov)) * FX_RATE * FX_RATE / EPS_FD;

            assertTrue(!computedUSD.contains(USD) && DoubleMath.fuzzyEquals(expectedFdUSD, 0d, eps));
            double expectedFdGBP = (test.presentValue(resetNotionalGBP, provUp) - test.presentValue(resetNotionalGBP, prov)) / EPS_FD;

            assertTrue(!computedGBP.contains(GBP) && DoubleMath.fuzzyEquals(expectedFdGBP, 0d, eps));
        }
Exemple #2
0
        /// <summary>
        /// Computes the price of a one-touch/no-touch option.
        /// </summary>
        /// <param name="spot">  the spot </param>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="costOfCarry">  the cost of carry </param>
        /// <param name="rate">  the interest rate </param>
        /// <param name="lognormalVol">  the lognormal volatility </param>
        /// <param name="barrier">  the barrier </param>
        /// <returns> the price </returns>
        public virtual double price(double spot, double timeToExpiry, double costOfCarry, double rate, double lognormalVol, SimpleConstantContinuousBarrier barrier)
        {
            ArgChecker.notNull(barrier, "barrier");
            bool   isKnockIn = barrier.KnockType.KnockIn;
            bool   isDown    = barrier.BarrierType.Down;
            double h         = barrier.BarrierLevel;

            ArgChecker.isFalse(isDown && spot <= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
            ArgChecker.isFalse(!isDown && spot >= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (UP and spot>=barrier).");
            double eta            = isDown ? 1 : -1;
            double df2            = Math.Exp(-rate * timeToExpiry);
            double lognormalVolSq = lognormalVol * lognormalVol;
            double lognormalVolT  = lognormalVol * Math.Sqrt(timeToExpiry);

            if (DoubleMath.fuzzyEquals(Math.Min(timeToExpiry, lognormalVolSq), 0d, SMALL))
            {
                return(isKnockIn ? 0d : df2);
            }
            double mu     = (costOfCarry - 0.5 * lognormalVolSq) / lognormalVolSq;
            double lambda = Math.Sqrt(mu * mu + 2 * rate / lognormalVolSq);
            double m1     = lognormalVolT * (1 + mu);
            double x2     = Math.Log(spot / h) / lognormalVolT + m1;
            double y2     = Math.Log(h / spot) / lognormalVolT + m1;
            double z      = Math.Log(h / spot) / lognormalVolT + lambda * lognormalVolT;
            double xE     = isKnockIn ? getF(spot, z, lognormalVolT, h, mu, lambda, eta) : getE(spot, df2, x2, y2, lognormalVolT, h, mu, eta);

            return(xE);
        }
        //-------------------------------------------------------------------------
        private void validateData(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData data)
        {
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.getTime(data.NumberOfSteps), volatilities.relativeTime(underlyingOption.Expiry), SMALL), "time to expiry mismatch between pricing option and trinomial tree data");
            ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.Spot, ratesProvider.fxRate(underlyingOption.Underlying.CurrencyPair), SMALL), "today's FX rate mismatch between rates provider and trinomial tree data");
        }
 /// <summary>
 /// {@inheritDoc} </summary>
 /// <exception cref="MathException"> If there are no real roots; if the Commons method could not evaluate the function; if the Commons method could not converge. </exception>
 public virtual double?[] getRoots(RealPolynomialFunction1D function)
 {
     ArgChecker.notNull(function, "function");
     try
     {
         Complex[]      roots     = ROOT_FINDER.solveAllComplex(function.Coefficients, 0);
         IList <double> realRoots = new List <double>();
         foreach (Complex c in roots)
         {
             if (DoubleMath.fuzzyEquals(c.Imaginary, 0d, EPS))
             {
                 realRoots.Add(c.Real);
             }
         }
         if (realRoots.Count == 0)
         {
             throw new MathException("Could not find any real roots");
         }
         return(realRoots.ToArray());
     }
     catch (TooManyEvaluationsException e)
     {
         throw new MathException(e);
     }
 }
Exemple #5
0
 /// <summary>
 /// Creates an instance.
 /// </summary>
 /// <param name="mu"> The location parameter </param>
 /// <param name="sigma"> The scale parameter, not negative or zero </param>
 /// <param name="ksi"> The shape parameter </param>
 public GeneralizedExtremeValueDistribution(double mu, double sigma, double ksi)
 {
     ArgChecker.isTrue(sigma >= 0, "sigma must be >= 0");
     _mu        = mu;
     _sigma     = sigma;
     _ksi       = ksi;
     _ksiIsZero = DoubleMath.fuzzyEquals(ksi, 0d, 1e-13);
 }
 /// <summary>
 /// Creates an instance.
 /// </summary>
 /// <param name="mu"> The location parameter </param>
 /// <param name="sigma"> The scale parameter </param>
 /// <param name="ksi"> The shape parameter </param>
 /// <param name="engine"> A uniform random number generator, not null </param>
 public GeneralizedParetoDistribution(double mu, double sigma, double ksi, RandomEngine engine)
 {
     ArgChecker.isTrue(sigma > 0, "sigma must be > 0");
     ArgChecker.isTrue(!DoubleMath.fuzzyEquals(ksi, 0d, 1e-15), "ksi cannot be zero");
     ArgChecker.notNull(engine, "engine");
     _mu     = mu;
     _sigma  = sigma;
     _ksi    = ksi;
     _engine = engine;
 }
 //-------------------------------------------------------------------------
 /// <summary>
 /// Compares each element in the array to zero within a tolerance.
 /// <para>
 /// An empty array returns true;
 /// </para>
 /// <para>
 /// The input array is not mutated.
 ///
 /// </para>
 /// </summary>
 /// <param name="array">  the array to check </param>
 /// <param name="tolerance">  the tolerance to use </param>
 /// <returns> true if the array is effectively equal to zero </returns>
 public static bool fuzzyEqualsZero(double[] array, double tolerance)
 {
     for (int i = 0; i < array.Length; i++)
     {
         if (!DoubleMath.fuzzyEquals(array[i], 0, tolerance))
         {
             return(false);
         }
     }
     return(true);
 }
Exemple #8
0
 //-------------------------------------------------------------------------
 /// <summary>
 /// Converts this amount to an equivalent amount the specified currency.
 /// <para>
 /// The result will be expressed in terms of the given currency, converting
 /// using the specified FX rate.
 /// </para>
 /// <para>
 /// For example, if this represents 'GBP 100' and this method is called with
 /// arguments {@code (USD, 1.6)} then the result will be 'USD 160'.
 ///
 /// </para>
 /// </summary>
 /// <param name="resultCurrency">  the currency of the result </param>
 /// <param name="fxRate">  the FX rate from this currency to the result currency </param>
 /// <returns> the converted instance, which should be expressed in the specified currency </returns>
 /// <exception cref="IllegalArgumentException"> if the FX is not 1 when no conversion is required </exception>
 public virtual Money convertedTo(Currency resultCurrency, decimal fxRate)
 {
     if (currency.Equals(resultCurrency))
     {
         if (DoubleMath.fuzzyEquals(fxRate.doubleValue(), 1d, 1e-8))
         {
             return(this);
         }
         throw new System.ArgumentException("FX rate must be 1 when no conversion required");
     }
     return(Money.of(resultCurrency, amount * fxRate));
 }
 /// <summary>
 /// Converts this amount to an equivalent amount the specified currency.
 /// <para>
 /// The result will be expressed in terms of the given currency, converting
 /// using the specified FX rate.
 /// </para>
 /// <para>
 /// For example, if this represents 'GBP 100' and this method is called with
 /// arguments {@code (USD, 1.6)} then the result will be 'USD 160'.
 ///
 /// </para>
 /// </summary>
 /// <param name="resultCurrency">  the currency of the result </param>
 /// <param name="fxRate">  the FX rate from this currency to the result currency </param>
 /// <returns> the converted instance, which should be expressed in the specified currency </returns>
 /// <exception cref="IllegalArgumentException"> if the FX is not 1 when no conversion is required </exception>
 public CurrencyAmount convertedTo(Currency resultCurrency, double fxRate)
 {
     if (currency.Equals(resultCurrency))
     {
         if (DoubleMath.fuzzyEquals(fxRate, 1d, 1e-8))
         {
             return(this);
         }
         throw new System.ArgumentException("FX rate must be 1 when no conversion required");
     }
     return(CurrencyAmount.of(resultCurrency, amount * fxRate));
 }
Exemple #10
0
        //-------------------------------------------------------------------------
        /// <summary>
        /// Computes the implied volatility.
        /// <para>
        /// If the volatility data is not zero, it is used as a starting point for the volatility search.
        /// </para>
        /// <para>
        /// Note that the 'numeraire' is a simple multiplier and is the responsibility of the caller.
        ///
        /// </para>
        /// </summary>
        /// <param name="optionPrice">  the price of the option </param>
        /// <param name="forward">  the forward value of the underlying </param>
        /// <param name="strike">  the strike </param>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="initialNormalVol">  the normal volatility used to start the search </param>
        /// <param name="numeraire">  the numeraire </param>
        /// <param name="putCall">  whether it is put or call </param>
        /// <returns> the implied volatility </returns>
        public static double impliedVolatility(double optionPrice, double forward, double strike, double timeToExpiry, double initialNormalVol, double numeraire, PutCall putCall)
        {
            double intrinsicPrice = numeraire * Math.Max(0, (putCall.Call ? 1 : -1) * (forward - strike));

            ArgChecker.isTrue(optionPrice > intrinsicPrice || DoubleMath.fuzzyEquals(optionPrice, intrinsicPrice, 1e-6), "Option price (" + optionPrice + ") less than intrinsic value (" + intrinsicPrice + ")");
            if (System.BitConverter.DoubleToInt64Bits(optionPrice) == Double.doubleToLongBits(intrinsicPrice))
            {
                return(0d);
            }
            double           sigma     = (Math.Abs(initialNormalVol) < 1e-10 ? 0.3 * forward : initialNormalVol);
            double           maxChange = 0.5 * forward;
            ValueDerivatives price     = priceAdjoint(forward, strike, timeToExpiry, sigma, numeraire, putCall);
            double           vega      = price.getDerivative(1);
            double           change    = (price.Value - optionPrice) / vega;
            double           sign      = Math.Sign(change);

            change = sign * Math.Min(maxChange, Math.Abs(change));
            if (change > 0 && change > sigma)
            {
                change = sigma;
            }
            int count = 0;

            while (Math.Abs(change) > EPS)
            {
                sigma -= change;
                price  = priceAdjoint(forward, strike, timeToExpiry, sigma, numeraire, putCall);
                vega   = price.getDerivative(1);
                change = (price.Value - optionPrice) / vega;
                sign   = Math.Sign(change);
                change = sign * Math.Min(maxChange, Math.Abs(change));
                if (change > 0 && change > sigma)
                {
                    change = sigma;
                }
                if (count++ > MAX_ITERATIONS)
                {
                    BracketRoot bracketer = new BracketRoot();
                    BisectionSingleRootFinder    rootFinder = new BisectionSingleRootFinder(EPS);
                    System.Func <double, double> func       = (double?volatility) =>
                    {
                        return(numeraire * NormalFormulaRepository.price(forward, strike, timeToExpiry, volatility.Value, putCall) - optionPrice);
                    };
                    double[] range = bracketer.getBracketedPoints(func, 0d, 10d);
                    return(rootFinder.getRoot(func, range[0], range[1]).Value);
                }
            }
            return(sigma);
        }
        /// <summary>
        /// Converts the polynomial to its monic form. If
        /// $$
        /// \begin{align*}
        /// P(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3 \dots + a_n x^n
        /// \end{align*}
        /// $$
        /// then the monic form is
        /// $$
        /// \begin{align*}
        /// P(x) = \lambda_0 + \lambda_1 x + \lambda_2 x^2 + \lambda_3 x^3 \dots + x^n
        /// \end{align*}
        /// $$
        /// where
        /// $$
        /// \begin{align*}
        /// \lambda_i = \frac{a_i}{a_n}
        /// \end{align*}
        /// $$
        /// </summary>
        /// <returns> the polynomial in monic form </returns>
        public virtual RealPolynomialFunction1D toMonic()
        {
            double an = _coefficients[_n - 1];

            if (DoubleMath.fuzzyEquals(an, (double)1, 1e-15))
            {
                return(new RealPolynomialFunction1D(Arrays.copyOf(_coefficients, _n)));
            }
            double[] rescaled = new double[_n];
            for (int i = 0; i < _n; i++)
            {
                rescaled[i] = _coefficients[i] / an;
            }
            return(new RealPolynomialFunction1D(rescaled));
        }
 /// <summary>
 /// Compares each element in the first array to the matching index in the second array within a tolerance.
 /// <para>
 /// If the arrays differ in length, false is returned.
 /// </para>
 /// <para>
 /// The input arrays are not mutated.
 ///
 /// </para>
 /// </summary>
 /// <param name="array1">  the first array to check </param>
 /// <param name="array2">  the second array to check </param>
 /// <param name="tolerance">  the tolerance to use </param>
 /// <returns> true if the arrays are effectively equal </returns>
 public static bool fuzzyEquals(double[] array1, double[] array2, double tolerance)
 {
     if (array1.Length != array2.Length)
     {
         return(false);
     }
     for (int i = 0; i < array1.Length; i++)
     {
         if (!DoubleMath.fuzzyEquals(array1[i], array2[i], tolerance))
         {
             return(false);
         }
     }
     return(true);
 }
Exemple #13
0
        private static CurveNode curveFxSwapCurveNode(string conventionStr, string timeStr, string label, QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order)
        {
            if (!DoubleMath.fuzzyEquals(spread, 0d, 1e-10d))
            {
                throw new System.ArgumentException("Additional spread must be zero for FX swaps");
            }
            Matcher matcher = SIMPLE_YMD_TIME_REGEX.matcher(timeStr.ToUpper(Locale.ENGLISH));

            if (!matcher.matches())
            {
                throw new System.ArgumentException(Messages.format("Invalid time format for FX swap: {}", timeStr));
            }
            Period           periodToEnd = Period.parse("P" + matcher.group(1));
            FxSwapConvention convention  = FxSwapConvention.of(conventionStr);
            FxSwapTemplate   template    = FxSwapTemplate.of(periodToEnd, convention);

            return(FxSwapCurveNode.builder().template(template).farForwardPointsId(quoteId).label(label).date(date).dateOrder(order).build());
        }
        public virtual double?[] getRoots(RealPolynomialFunction1D function)
        {
            ArgChecker.notNull(function, "function");
            double[] coefficients = function.Coefficients;
            if (coefficients.Length != 4)
            {
                throw new System.ArgumentException("Function is not a cubic");
            }
            ComplexNumber[] result = ROOT_FINDER.getRoots(function);
            IList <double>  reals  = new List <double>();

            foreach (ComplexNumber c in result)
            {
                if (DoubleMath.fuzzyEquals(c.Imaginary, 0d, 1e-16))
                {
                    reals.Add(c.Real);
                }
            }
            ArgChecker.isTrue(reals.Count > 0, "Could not find any real roots");
            return(reals.toArray(EMPTY_ARRAY));
        }
        /// <summary>
        /// {@inheritDoc} </summary>
        /// <exception cref="IllegalArgumentException"> If the function is not cubic </exception>
        public virtual ComplexNumber[] getRoots(RealPolynomialFunction1D function)
        {
            ArgChecker.notNull(function, "function");
            double[] coefficients = function.Coefficients;
            ArgChecker.isTrue(coefficients.Length == 4, "Function is not a cubic");
            double divisor  = coefficients[3];
            double a        = coefficients[2] / divisor;
            double b        = coefficients[1] / divisor;
            double c        = coefficients[0] / divisor;
            double aSq      = a * a;
            double q        = (aSq - 3 * b) / 9;
            double r        = (2 * a * aSq - 9 * a * b + 27 * c) / 54;
            double rSq      = r * r;
            double qCb      = q * q * q;
            double constant = a / 3;

            if (rSq < qCb)
            {
                double mult  = -2 * Math.Sqrt(q);
                double theta = Math.Acos(r / Math.Sqrt(qCb));
                return(new ComplexNumber[]
                {
                    new ComplexNumber(mult * Math.Cos(theta / 3) - constant, 0),
                    new ComplexNumber(mult * Math.Cos((theta + TWO_PI) / 3) - constant, 0),
                    new ComplexNumber(mult * Math.Cos((theta - TWO_PI) / 3) - constant, 0)
                });
            }
            double s         = -Math.Sign(r) * Math.cbrt(Math.Abs(r) + Math.Sqrt(rSq - qCb));
            double t         = DoubleMath.fuzzyEquals(s, 0d, 1e-16) ? 0 : q / s;
            double sum       = s + t;
            double real      = -0.5 * sum - constant;
            double imaginary = Math.Sqrt(3) * (s - t) / 2;

            return(new ComplexNumber[]
            {
                new ComplexNumber(sum - constant, 0),
                new ComplexNumber(real, imaginary),
                new ComplexNumber(real, -imaginary)
            });
        }
Exemple #16
0
        public virtual void test_pv01_quote()
        {
            FixedCouponBondTradeCalculationFunction <FixedCouponBondTrade> function = FixedCouponBondTradeCalculationFunction.TRADE;
            ScenarioMarketData                    md                      = marketData();
            LegalEntityDiscountingProvider        provider                = LOOKUP.marketDataView(md.scenario(0)).discountingProvider();
            DiscountingFixedCouponBondTradePricer pricer                  = DiscountingFixedCouponBondTradePricer.DEFAULT;
            PointSensitivities                    pvPointSens             = pricer.presentValueSensitivity(RTRADE, provider);
            CurrencyParameterSensitivities        pvParamSens             = provider.parameterSensitivity(pvPointSens);
            CurrencyParameterSensitivities        expectedPv01CalBucketed = MQ_CALC.sensitivity(pvParamSens, provider).multipliedBy(1e-4);
            MultiCurrencyAmount                   expectedPv01Cal         = expectedPv01CalBucketed.total();

            ISet <Measure> measures = ImmutableSet.of(Measures.PV01_MARKET_QUOTE_SUM, Measures.PV01_MARKET_QUOTE_BUCKETED);
//JAVA TO C# CONVERTER WARNING: Java wildcard generics have no direct equivalent in .NET:
//ORIGINAL LINE: java.util.Map<com.opengamma.strata.calc.Measure, com.opengamma.strata.collect.result.Result<?>> computed = function.calculate(TRADE, measures, PARAMS, md, REF_DATA);
            IDictionary <Measure, Result <object> > computed = function.calculate(TRADE, measures, PARAMS, md, REF_DATA);
            MultiCurrencyScenarioArray sumComputed           = (MultiCurrencyScenarioArray)computed[Measures.PV01_MARKET_QUOTE_SUM].Value;
            ScenarioArray <CurrencyParameterSensitivities> bucketedComputed = (ScenarioArray <CurrencyParameterSensitivities>)computed[Measures.PV01_MARKET_QUOTE_BUCKETED].Value;

            assertEquals(sumComputed.ScenarioCount, 1);
            assertEquals(sumComputed.get(0).Currencies, ImmutableSet.of(GBP));
            assertTrue(DoubleMath.fuzzyEquals(sumComputed.get(0).getAmount(GBP).Amount, expectedPv01Cal.getAmount(GBP).Amount, 1.0e-10));
            assertEquals(bucketedComputed.ScenarioCount, 1);
            assertTrue(bucketedComputed.get(0).equalWithTolerance(expectedPv01CalBucketed, 1.0e-10));
        }
        public double volatility(double forward, double strike, double timeToExpiry, double alpha, double beta, double rho, double nu)
        {
            ArgChecker.isTrue(forward > 0.0, "forward must be greater than zero");
            ArgChecker.isTrue(strike >= 0.0, "strike must be greater than zero");
            ArgChecker.isTrue(timeToExpiry >= 0.0, "timeToExpiry must be greater than zero");

            if (alpha == 0.0)
            {
                return(0.0);
            }
            double cutoff = forward * CUTOFF_MONEYNESS;
            double k;

            if (strike < cutoff)
            {
                log.info("Given strike of {} is less than cutoff at {}, therefore the strike is taken as {}", new object[] { strike, cutoff, cutoff });
                k = cutoff;
            }
            else
            {
                k = strike;
            }
            double vol, z, zOverChi;
            double beta1 = 1 - beta;

            if (DoubleMath.fuzzyEquals(forward, k, ATM_EPS))
            {
                double f1 = Math.Pow(forward, beta1);
                vol = alpha * (1 + timeToExpiry * (beta1 * beta1 * alpha * alpha / 24 / f1 / f1 + rho * alpha * beta * nu / 4 / f1 + nu * nu * (2 - 3 * rho * rho) / 24)) / f1;
            }
            else
            {
                if (DoubleMath.fuzzyEquals(beta, 0, BETA_EPS))
                {
                    double ln = Math.Log(forward / k);
                    z        = nu * Math.Sqrt(forward * k) * ln / alpha;
                    zOverChi = getZOverChi(rho, z);
                    vol      = alpha * ln * zOverChi * (1 + timeToExpiry * (alpha * alpha / forward / k + nu * nu * (2 - 3 * rho * rho)) / 24) / (forward - k);
                }
                else if (DoubleMath.fuzzyEquals(beta, 1, BETA_EPS))
                {
                    double ln = Math.Log(forward / k);
                    z        = nu * ln / alpha;
                    zOverChi = getZOverChi(rho, z);
                    vol      = alpha * zOverChi * (1 + timeToExpiry * (rho * alpha * nu / 4 + nu * nu * (2 - 3 * rho * rho) / 24));
                }
                else
                {
                    double ln       = Math.Log(forward / k);
                    double f1       = Math.Pow(forward * k, beta1);
                    double f1Sqrt   = Math.Sqrt(f1);
                    double lnBetaSq = Math.Pow(beta1 * ln, 2);
                    z        = nu * f1Sqrt * ln / alpha;
                    zOverChi = getZOverChi(rho, z);
                    double first  = alpha / (f1Sqrt * (1 + lnBetaSq / 24 + lnBetaSq * lnBetaSq / 1920));
                    double second = zOverChi;
                    double third  = 1 + timeToExpiry * (beta1 * beta1 * alpha * alpha / 24 / f1 + rho * nu * beta * alpha / 4 / f1Sqrt + nu * nu * (2 - 3 * rho * rho) / 24);
                    vol = first * second * third;
                }
            }
            //There is nothing to prevent the nu * nu * (2 - 3 * rho * rho) / 24 to be large negative, and hence the volatility negative
            return(Math.Max(MIN_VOL, vol));
        }
Exemple #18
0
        /// <summary>
        /// Computes the price of a barrier option.
        /// </summary>
        /// <param name="spot">  the spot </param>
        /// <param name="strike">  the strike </param>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="costOfCarry">  the cost of carry </param>
        /// <param name="rate">  the interest rate </param>
        /// <param name="lognormalVol">  the lognormal volatility </param>
        /// <param name="isCall">  true if call, false otherwise </param>
        /// <param name="barrier">  the barrier </param>
        /// <returns> the price </returns>
        public virtual double price(double spot, double strike, double timeToExpiry, double costOfCarry, double rate, double lognormalVol, bool isCall, SimpleConstantContinuousBarrier barrier)
        {
            ArgChecker.notNull(barrier, "barrier");
            bool   isKnockIn = barrier.KnockType.KnockIn;
            bool   isDown    = barrier.BarrierType.Down;
            double h         = barrier.BarrierLevel;

            ArgChecker.isFalse(isDown && spot <= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
            ArgChecker.isFalse(!isDown && spot >= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (UP and spot>=barrier).");
            int    phi     = isCall ? 1 : -1;
            double eta     = isDown ? 1 : -1;
            double df1     = Math.Exp(timeToExpiry * (costOfCarry - rate));
            double df2     = Math.Exp(-rate * timeToExpiry);
            double sigmaSq = lognormalVol * lognormalVol;
            double sigmaT  = lognormalVol * Math.Sqrt(timeToExpiry);

            if (DoubleMath.fuzzyEquals(Math.Min(timeToExpiry, sigmaSq), 0d, SMALL))
            {
                if (isKnockIn)
                {
                    return(0d);
                }
                double dscFwd = df1 * spot;
                double dscStr = df2 * strike;
                return(isCall ? (dscFwd >= dscStr ? dscFwd - dscStr : 0d) : (dscStr >= dscFwd ? dscStr - dscFwd : 0d));
            }
            double mu = (costOfCarry - 0.5 * sigmaSq) / sigmaSq;
            double m1 = sigmaT * (1 + mu);
            double x1 = Math.Log(spot / strike) / sigmaT + m1;
            double x2 = Math.Log(spot / h) / sigmaT + m1;
            double y1 = Math.Log(h * h / spot / strike) / sigmaT + m1;
            double y2 = Math.Log(h / spot) / sigmaT + m1;
            double xA = getA(spot, strike, df1, df2, x1, sigmaT, phi);
            double xB = getA(spot, strike, df1, df2, x2, sigmaT, phi);
            double xC = getC(spot, strike, df1, df2, y1, sigmaT, h, mu, phi, eta);
            double xD = getC(spot, strike, df1, df2, y2, sigmaT, h, mu, phi, eta);

            if (isKnockIn)
            {     // KnockIn
                if (isDown)
                {
                    if (isCall)
                    {
                        return(strike > h ? xC : xA - xB + xD);
                    }
                    return(strike > h ? xB - xC + xD : xA);
                }
                if (isCall)
                {
                    return(strike > h ? xA : xB - xC + xD);
                }
                return(strike > h ? xA - xB + xD : xC);
            }
            // KnockOut
            if (isDown)
            {
                if (isCall)
                {
                    return(strike > h ? xA - xC : xB - xD);
                }
                return(strike > h ? xA - xB + xC - xD : 0d);
            }
            if (isCall)
            {
                return(strike > h ? 0d : xA - xB + xC - xD);
            }
            return(strike > h ? xB - xD : xA - xC);
        }
Exemple #19
0
        /// <summary>
        /// Computes the price and derivatives of a barrier option.
        ///
        /// The derivatives are [0] spot, [1] strike, [2] rate, [3] cost-of-carry, [4] volatility, [5] timeToExpiry, [6] spot twice
        /// </summary>
        /// <param name="spot">  the spot </param>
        /// <param name="strike">  the strike </param>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="costOfCarry">  the cost of carry </param>
        /// <param name="rate">  the interest rate </param>
        /// <param name="lognormalVol">  the lognormal volatility </param>
        /// <param name="isCall">  true if call, false otherwise </param>
        /// <param name="barrier">  the barrier </param>
        /// <returns> the price and derivatives </returns>
        public virtual ValueDerivatives priceAdjoint(double spot, double strike, double timeToExpiry, double costOfCarry, double rate, double lognormalVol, bool isCall, SimpleConstantContinuousBarrier barrier)
        {
            ArgChecker.notNull(barrier, "barrier");
            double[] derivatives = new double[7];
            bool     isKnockIn   = barrier.KnockType.KnockIn;
            bool     isDown      = barrier.BarrierType.Down;
            double   h           = barrier.BarrierLevel;

            ArgChecker.isFalse(isDown && spot <= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
            ArgChecker.isFalse(!isDown && spot >= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (UP and spot>=barrier).");
            int    phi            = isCall ? 1 : -1;
            double eta            = isDown ? 1 : -1;
            double df1            = Math.Exp(timeToExpiry * (costOfCarry - rate));
            double df2            = Math.Exp(-rate * timeToExpiry);
            double lognormalVolSq = lognormalVol * lognormalVol;
            double lognormalVolT  = lognormalVol * Math.Sqrt(timeToExpiry);

            if (DoubleMath.fuzzyEquals(Math.Min(timeToExpiry, lognormalVolSq), 0d, SMALL))
            {
                if (isKnockIn)
                {
                    return(ValueDerivatives.of(0d, DoubleArray.filled(7)));
                }
                double dscFwd = df1 * spot;
                double dscStr = df2 * strike;
                double price  = 0d;
                if (isCall)
                {
                    if (dscFwd >= dscStr)
                    {
                        price          = dscFwd - dscStr;
                        derivatives[0] = df1;
                        derivatives[1] = -df2;
                        derivatives[2] = -timeToExpiry * price;
                        derivatives[3] = timeToExpiry * dscFwd;
                        derivatives[5] = (costOfCarry - rate) * dscFwd + rate * dscStr;
                    }
                }
                else
                {
                    if (dscStr >= dscFwd)
                    {
                        price          = dscStr - dscFwd;
                        derivatives[0] = -df1;
                        derivatives[1] = df2;
                        derivatives[2] = -timeToExpiry * price;
                        derivatives[3] = -timeToExpiry * dscFwd;
                        derivatives[5] = -rate * dscStr - (costOfCarry - rate) * dscFwd;
                    }
                }
                return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives)));
            }
            double mu = (costOfCarry - 0.5 * lognormalVolSq) / lognormalVolSq;
            double m1 = lognormalVolT * (1 + mu);
            double x1 = Math.Log(spot / strike) / lognormalVolT + m1;
            double x2 = Math.Log(spot / h) / lognormalVolT + m1;
            double y1 = Math.Log(h * h / spot / strike) / lognormalVolT + m1;
            double y2 = Math.Log(h / spot) / lognormalVolT + m1;

            double[] aDerivFirst = new double[6];
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] aDerivSecond = new double[2][2];
            double[][] aDerivSecond = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
            double     xA           = getAAdjoint(spot, strike, df1, df2, x1, lognormalVolT, phi, aDerivFirst, aDerivSecond);

            double[] bDerivFirst = new double[6];
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] bDerivSecond = new double[2][2];
            double[][] bDerivSecond = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
            double     xB           = getAAdjoint(spot, strike, df1, df2, x2, lognormalVolT, phi, bDerivFirst, bDerivSecond);

            double[] cDerivFirst = new double[7];
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] cDerivSecond = new double[2][2];
            double[][] cDerivSecond = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
            double     xC           = getCAdjoint(spot, strike, df1, df2, y1, lognormalVolT, h, mu, phi, eta, cDerivFirst, cDerivSecond);

            double[] dDerivFirst = new double[7];
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] dDerivSecond = new double[2][2];
            double[][] dDerivSecond = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
            double     xD           = getCAdjoint(spot, strike, df1, df2, y2, lognormalVolT, h, mu, phi, eta, dDerivFirst, dDerivSecond);
            double     xDBar        = 0d;
            double     xCBar        = 0d;
            double     xBBar        = 0d;
            double     xABar        = 0d;
            double     price;

            if (isKnockIn)
            {         // IN start
                if (isDown)
                {     // DOWN start
                    if (isCall)
                    { // Call start
                        if (strike > h)
                        {
                            xCBar = 1d;
                            price = xC;
                        }
                        else
                        {
                            xABar = 1d;
                            xBBar = -1d;
                            xDBar = 1d;
                            price = xA - xB + xD;
                        }
                    }
                    else
                    {     // Put start
                        if (strike > h)
                        {
                            xBBar = 1d;
                            xCBar = -1d;
                            xDBar = 1d;
                            price = xB - xC + xD;
                        }
                        else
                        {
                            xABar = 1d;
                            price = xA;
                        }
                    }     // DOWN end
                }
                else
                {   // UP start
                    if (isCall)
                    {
                        if (strike > h)
                        {
                            xABar = 1d;
                            price = xA;
                        }
                        else
                        {
                            xBBar = 1d;
                            xCBar = -1d;
                            xDBar = 1d;
                            price = xB - xC + xD;
                        }
                    }
                    else
                    {
                        if (strike > h)
                        {
                            xABar = 1d;
                            xBBar = -1d;
                            xDBar = 1d;
                            price = xA - xB + xD;
                        }
                        else
                        {
                            xCBar = 1d;
                            price = xC;
                        }
                    } // UP end
                }     // IN end
            }
            else
            {         // OUT start
                if (isDown)
                {     // DOWN start
                    if (isCall)
                    { // CALL start
                        if (strike > h)
                        {
                            xABar = 1d;
                            xCBar = -1d;
                            price = xA - xC;
                        }
                        else
                        {
                            xBBar = 1d;
                            xDBar = -1d;
                            price = xB - xD;
                        }
                    }
                    else
                    {     // PUT start
                        if (strike > h)
                        {
                            xABar = 1d;
                            xBBar = -1d;
                            xCBar = 1d;
                            xDBar = -1d;
                            price = xA - xB + xC - xD;
                        }
                        else
                        {
                            price = 0d;
                        } // PUT end
                    }     // DOWN end
                }
                else
                {   // UP start
                    if (isCall)
                    {
                        if (strike > h)
                        {
                            price = 0d;
                        }
                        else
                        {
                            xABar = 1d;
                            xBBar = -1d;
                            xCBar = 1d;
                            xDBar = -1d;
                            price = xA - xB + xC - xD;
                        }
                    }
                    else
                    {
                        if (strike > h)
                        {
                            xBBar = 1d;
                            xDBar = -1d;
                            price = xB - xD;
                        }
                        else
                        {
                            xABar = 1d;
                            xCBar = -1d;
                            price = xA - xC;
                        } // PUT end
                    }     // UP end
                }         // OUT end
            }
            double dxyds             = 1d / spot / lognormalVolT;
            double x1Bar             = aDerivFirst[4] * xABar;
            double x2Bar             = bDerivFirst[4] * xBBar;
            double y1Bar             = cDerivFirst[4] * xCBar;
            double y2Bar             = dDerivFirst[4] * xDBar;
            double m1Bar             = x1Bar + x2Bar + y1Bar + y2Bar;
            double muBar             = cDerivFirst[6] * xCBar + dDerivFirst[6] * xDBar + lognormalVolT * m1Bar;
            double lognormalVolTBar  = aDerivFirst[5] * xABar + bDerivFirst[5] * xBBar + cDerivFirst[5] * xCBar + dDerivFirst[5] * xDBar - Math.Log(h / spot) / (lognormalVolT * lognormalVolT) * y2Bar - Math.Log(h * h / spot / strike) / (lognormalVolT * lognormalVolT) * y1Bar - Math.Log(spot / h) / (lognormalVolT * lognormalVolT) * x2Bar - Math.Log(spot / strike) / (lognormalVolT * lognormalVolT) * x1Bar + (1d + mu) * m1Bar;
            double lognormalVolSqBar = -costOfCarry / (lognormalVolSq * lognormalVolSq) * muBar;
            double df2Bar            = aDerivFirst[3] * xABar + bDerivFirst[3] * xBBar + cDerivFirst[3] * xCBar + dDerivFirst[3] * xDBar;
            double df1Bar            = aDerivFirst[2] * xABar + bDerivFirst[2] * xBBar + cDerivFirst[2] * xCBar + dDerivFirst[2] * xDBar;

            derivatives[0] = aDerivFirst[0] * xABar + bDerivFirst[0] * xBBar + cDerivFirst[0] * xCBar + dDerivFirst[0] * xDBar + x1Bar * dxyds + x2Bar * dxyds - y1Bar * dxyds - y2Bar * dxyds;
            derivatives[1] = aDerivFirst[1] * xABar + bDerivFirst[1] * xBBar + cDerivFirst[1] * xCBar + dDerivFirst[1] * xDBar - (x1Bar + y1Bar) / strike / lognormalVolT;
            derivatives[2] = -timeToExpiry * (df1 * df1Bar + df2 * df2Bar);
            derivatives[3] = timeToExpiry * df1 * df1Bar + muBar / lognormalVolSq;
            derivatives[4] = 2d * lognormalVol * lognormalVolSqBar + Math.Sqrt(timeToExpiry) * lognormalVolTBar;
            derivatives[5] = (costOfCarry - rate) * df1 * df1Bar - rate * df2 * df2Bar + lognormalVolTBar * lognormalVolT * 0.5 / timeToExpiry;
            derivatives[6] = aDerivSecond[0][0] * xABar + bDerivSecond[0][0] * xBBar + cDerivSecond[0][0] * xCBar + dDerivSecond[0][0] * xDBar + 2d * xABar * aDerivSecond[0][1] * dxyds + 2d * xBBar * bDerivSecond[0][1] * dxyds - 2d * xCBar * cDerivSecond[0][1] * dxyds - 2d * xDBar * dDerivSecond[0][1] * dxyds + xABar * aDerivSecond[1][1] * dxyds * dxyds + xBBar * bDerivSecond[1][1] * dxyds * dxyds + xCBar * cDerivSecond[1][1] * dxyds * dxyds + xDBar * dDerivSecond[1][1] * dxyds * dxyds - x1Bar * dxyds / spot - x2Bar * dxyds / spot + y1Bar * dxyds / spot + y2Bar * dxyds / spot;
            return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives)));
        }
        /// <summary>
        /// Computes the first and second order derivatives of the Black implied volatility in the SABR model.
        /// <para>
        /// The first derivative values will be stored in the input array {@code volatilityD}
        /// The array contains, [0] Derivative w.r.t the forward, [1] the derivative w.r.t the strike, [2] the derivative w.r.t. to alpha,
        /// [3] the derivative w.r.t. to beta, [4] the derivative w.r.t. to rho, and [5] the derivative w.r.t. to nu.
        /// Thus the length of the array should be 6.
        /// </para>
        /// <para>
        /// The second derivative values will be stored in the input array {@code volatilityD2}.
        /// Only the second order derivative with respect to the forward and strike are implemented.
        /// The array contains [0][0] forward-forward; [0][1] forward-strike; [1][1] strike-strike.
        /// Thus the size should be 2 x 2.
        /// </para>
        /// <para>
        /// Around ATM, a first order expansion is used to due to some 0/0-type indetermination.
        /// The second order derivative produced is poor around ATM.
        ///
        /// </para>
        /// </summary>
        /// <param name="forward">  the forward value of the underlying </param>
        /// <param name="strike">  the strike value of the option </param>
        /// <param name="timeToExpiry">  the time to expiry of the option </param>
        /// <param name="data">  the SABR data. </param>
        /// <param name="volatilityD">  the array used to return the first order derivative </param>
        /// <param name="volatilityD2">  the array of array used to return the second order derivative </param>
        /// <returns> the Black implied volatility </returns>
        public override double volatilityAdjoint2(double forward, double strike, double timeToExpiry, SabrFormulaData data, double[] volatilityD, double[][] volatilityD2)
        {
            double k     = Math.Max(strike, 0.000001);
            double alpha = data.Alpha;
            double beta  = data.Beta;
            double rho   = data.Rho;
            double nu    = data.Nu;
            // Forward
            double h0 = (1 - beta) / 2;
            double h1 = forward * k;
            double h1h0 = Math.Pow(h1, h0);
            double h12 = h1h0 * h1h0;
            double h2 = Math.Log(forward / k);
            double h22 = h2 * h2;
            double h23 = h22 * h2;
            double h24 = h23 * h2;
            double f1 = h1h0 * (1 + h0 * h0 / 6.0 * (h22 + h0 * h0 / 20.0 * h24));
            double f2 = nu / alpha * h1h0 * h2;
            double f3 = h0 * h0 / 6.0 * alpha * alpha / h12 + rho * beta * nu * alpha / 4.0 / h1h0 + (2 - 3 * rho * rho) / 24.0 * nu * nu;
            double sqrtf2 = Math.Sqrt(1 - 2 * rho * f2 + f2 * f2);
            double f2x = 0.0;
            double x = 0.0, xp = 0, xpp = 0;

            if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z))
            {
                f2x = 1.0 - 0.5 * f2 * rho;   //small f2 expansion to f2^2 terms
            }
            else
            {
                if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS))
                {
                    x = f2 < 1.0 ? -Math.Log(1.0 - f2) - 0.5 * Math.Pow(f2 / (f2 - 1.0), 2) * (1.0 - rho) : Math.Log(2.0 * f2 - 2.0) - Math.Log(1.0 - rho);
                }
                else
                {
                    x = Math.Log((sqrtf2 + f2 - rho) / (1 - rho));
                }
                xp  = 1.0 / sqrtf2;
                xpp = (rho - f2) / Math.Pow(sqrtf2, 3.0);
                f2x = f2 / x;
            }
            double sigma = Math.Max(MIN_VOL, alpha / f1 * f2x * (1 + f3 * timeToExpiry));
            // First level
            double h0Dbeta  = -0.5;
            double sigmaDf1 = -sigma / f1;
            double sigmaDf2 = 0;

            if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z))
            {
                sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * -0.5 * rho;
            }
            else
            {
                sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * (1.0 / x - f2 * xp / (x * x));
            }
            double sigmaDf3 = alpha / f1 * f2x * timeToExpiry;
            double sigmaDf4 = f2x / f1 * (1 + f3 * timeToExpiry);
            double sigmaDx  = -alpha / f1 * f2 / (x * x) * (1 + f3 * timeToExpiry);

//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] sigmaD2ff = new double[3][3];
            double[][] sigmaD2ff = RectangularArrays.ReturnRectangularDoubleArray(3, 3);
            sigmaD2ff[0][0] = -sigmaDf1 / f1 + sigma / (f1 * f1);     //OK
            sigmaD2ff[0][1] = -sigmaDf2 / f1;
            sigmaD2ff[0][2] = -sigmaDf3 / f1;
            if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z))
            {
                sigmaD2ff[1][2] = alpha / f1 * -0.5 * rho * timeToExpiry;
            }
            else
            {
                sigmaD2ff[1][1] = alpha / f1 * (1 + f3 * timeToExpiry) * (-2 * xp / (x * x) - f2 * xpp / (x * x) + 2 * f2 * xp * xp / (x * x * x));
                sigmaD2ff[1][2] = alpha / f1 * timeToExpiry * (1.0 / x - f2 * xp / (x * x));
            }
            sigmaD2ff[2][2] = 0.0;
            //      double sigma = alpha / f1 * f2x * (1 + f3 * theta);
            // Second level
            double[] f1Dh = new double[3];
            double[] f2Dh = new double[3];
            double[] f3Dh = new double[3];
            f1Dh[0] = h1h0 * (h0 * (h22 / 3.0 + h0 * h0 / 40.0 * h24)) + Math.Log(h1) * f1;
            f1Dh[1] = h0 * f1 / h1;
            f1Dh[2] = h1h0 * (h0 * h0 / 6.0 * (2.0 * h2 + h0 * h0 / 5.0 * h23));
            f2Dh[0] = Math.Log(h1) * f2;
            f2Dh[1] = h0 * f2 / h1;
            f2Dh[2] = nu / alpha * h1h0;
            f3Dh[0] = h0 / 3.0 * alpha * alpha / h12 - 2 * h0 * h0 / 6.0 * alpha * alpha / h12 * Math.Log(h1) - rho * beta * nu * alpha / 4.0 / h1h0 * Math.Log(h1);
            f3Dh[1] = -2 * h0 * h0 / 6.0 * alpha * alpha / h12 * h0 / h1 - rho * beta * nu * alpha / 4.0 / h1h0 * h0 / h1;
            f3Dh[2] = 0.0;
            double[] f1Dp = new double[4];     // Derivative to sabr parameters
            double[] f2Dp = new double[4];
            double[] f3Dp = new double[4];
            double[] f4Dp = new double[4];
            f1Dp[0] = 0.0;
            f1Dp[1] = f1Dh[0] * h0Dbeta;
            f1Dp[2] = 0.0;
            f1Dp[3] = 0.0;
            f2Dp[0] = -f2 / alpha;
            f2Dp[1] = f2Dh[0] * h0Dbeta;
            f2Dp[2] = 0.0;
            f2Dp[3] = h1h0 * h2 / alpha;
            f3Dp[0] = h0 * h0 / 3.0 * alpha / h12 + rho * beta * nu / 4.0 / h1h0;
            f3Dp[1] = rho * nu * alpha / 4.0 / h1h0 + f3Dh[0] * h0Dbeta;
            f3Dp[2] = beta * nu * alpha / 4.0 / h1h0 - rho / 4.0 * nu * nu;
            f3Dp[3] = rho * beta * alpha / 4.0 / h1h0 + (2 - 3 * rho * rho) / 12.0 * nu;
            f4Dp[0] = 1.0;
            f4Dp[1] = 0.0;
            f4Dp[2] = 0.0;
            f4Dp[3] = 0.0;
            double sigmaDh1 = sigmaDf1 * f1Dh[1] + sigmaDf2 * f2Dh[1] + sigmaDf3 * f3Dh[1];
            double sigmaDh2 = sigmaDf1 * f1Dh[2] + sigmaDf2 * f2Dh[2] + sigmaDf3 * f3Dh[2];

//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] f1D2hh = new double[2][2]; // No h0
            double[][] f1D2hh = RectangularArrays.ReturnRectangularDoubleArray(2, 2);     // No h0
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] f2D2hh = new double[2][2];
            double[][] f2D2hh = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] f3D2hh = new double[2][2];
            double[][] f3D2hh = RectangularArrays.ReturnRectangularDoubleArray(2, 2);
            f1D2hh[0][0] = h0 * (h0 - 1) * f1 / (h1 * h1);
            f1D2hh[0][1] = h0 * h1h0 / h1 * h0 * h0 / 6.0 * (2.0 * h2 + 4.0 * h0 * h0 / 20.0 * h23);
            f1D2hh[1][1] = h1h0 * (h0 * h0 / 6.0 * (2.0 + 12.0 * h0 * h0 / 20.0 * h2));
            f2D2hh[0][0] = h0 * (h0 - 1) * f2 / (h1 * h1);
            f2D2hh[0][1] = nu / alpha * h0 * h1h0 / h1;
            f2D2hh[1][1] = 0.0;
            f3D2hh[0][0] = 2 * h0 * (2 * h0 + 1) * h0 * h0 / 6.0 * alpha * alpha / (h12 * h1 * h1) + h0 * (h0 + 1) * rho * beta * nu * alpha / 4.0 / (h1h0 * h1 * h1);
            f3D2hh[0][1] = 0.0;
            f3D2hh[1][1] = 0.0;
//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
//ORIGINAL LINE: double[][] sigmaD2hh = new double[2][2]; // No h0
            double[][] sigmaD2hh = RectangularArrays.ReturnRectangularDoubleArray(2, 2);     // No h0
            for (int loopx = 0; loopx < 2; loopx++)
            {
                for (int loopy = loopx; loopy < 2; loopy++)
                {
                    sigmaD2hh[loopx][loopy] = (sigmaD2ff[0][0] * f1Dh[loopy + 1] + sigmaD2ff[0][1] * f2Dh[loopy + 1] + sigmaD2ff[0][2] * f3Dh[loopy + 1]) * f1Dh[loopx + 1] + sigmaDf1 * f1D2hh[loopx][loopy] + (sigmaD2ff[0][1] * f1Dh[loopy + 1] + sigmaD2ff[1][1] * f2Dh[loopy + 1] + sigmaD2ff[1][2] * f3Dh[loopy + 1]) * f2Dh[loopx + 1] + sigmaDf2 * f2D2hh[loopx][loopy] + (sigmaD2ff[0][2] * f1Dh[loopy + 1] + sigmaD2ff[1][2] * f2Dh[loopy + 1] + sigmaD2ff[2][2] * f3Dh[loopy + 1]) * f3Dh[loopx + 1] + sigmaDf3 * f3D2hh[loopx][loopy];
                }
            }
            // Third level
            double h1Df   = k;
            double h1Dk   = forward;
            double h1D2ff = 0.0;
            double h1D2kf = 1.0;
            double h1D2kk = 0.0;
            double h2Df   = 1.0 / forward;
            double h2Dk   = -1.0 / k;
            double h2D2ff = -1 / (forward * forward);
            double h2D2fk = 0.0;
            double h2D2kk = 1.0 / (k * k);

            volatilityD[0] = sigmaDh1 * h1Df + sigmaDh2 * h2Df;
            volatilityD[1] = sigmaDh1 * h1Dk + sigmaDh2 * h2Dk;
            volatilityD[2] = sigmaDf1 * f1Dp[0] + sigmaDf2 * f2Dp[0] + sigmaDf3 * f3Dp[0] + sigmaDf4 * f4Dp[0];
            volatilityD[3] = sigmaDf1 * f1Dp[1] + sigmaDf2 * f2Dp[1] + sigmaDf3 * f3Dp[1] + sigmaDf4 * f4Dp[1];
            if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z))
            {
                volatilityD[4] = -0.5 * f2 + sigmaDf3 * f3Dp[2];
            }
            else
            {
                double xDr;
                if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS))
                {
                    xDr = f2 > 1.0 ? 1.0 / (1.0 - rho) + (0.5 - f2) / (f2 - 1.0) / (f2 - 1.0) : 0.5 * Math.Pow(f2 / (1.0 - f2), 2.0) + 0.25 * (f2 - 4.0) * Math.Pow(f2 / (f2 - 1.0), 3) / (f2 - 1.0) * (1.0 - rho);
                    if (Doubles.isFinite(xDr))
                    {
                        volatilityD[4] = sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2];
                    }
                    else
                    {
                        volatilityD[4] = double.NegativeInfinity;
                    }
                }
                else
                {
                    xDr            = (-f2 / sqrtf2 - 1 + (sqrtf2 + f2 - rho) / (1 - rho)) / (sqrtf2 + f2 - rho);
                    volatilityD[4] = sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2];
                }
            }
            volatilityD[5]     = sigmaDf1 * f1Dp[3] + sigmaDf2 * f2Dp[3] + sigmaDf3 * f3Dp[3] + sigmaDf4 * f4Dp[3];
            volatilityD2[0][0] = (sigmaD2hh[0][0] * h1Df + sigmaD2hh[0][1] * h2Df) * h1Df + sigmaDh1 * h1D2ff + (sigmaD2hh[0][1] * h1Df + sigmaD2hh[1][1] * h2Df) * h2Df + sigmaDh2 * h2D2ff;
            volatilityD2[0][1] = (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Df + sigmaDh1 * h1D2kf + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Df + sigmaDh2 * h2D2fk;
            volatilityD2[1][0] = volatilityD2[0][1];
            volatilityD2[1][1] = (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Dk + sigmaDh1 * h1D2kk + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Dk + sigmaDh2 * h2D2kk;
            return(sigma);
        }
Exemple #21
0
        /// <summary>
        /// Computes the price and derivatives of a one-touch/no-touch option.
        /// <para>
        /// The derivatives are [0] spot, [1] rate, [2] cost-of-carry, [3] volatility, [4] timeToExpiry, [5] spot twice.
        ///
        /// </para>
        /// </summary>
        /// <param name="spot">  the spot </param>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="costOfCarry">  the cost of carry </param>
        /// <param name="rate">  the interest rate </param>
        /// <param name="lognormalVol">  the lognormal volatility </param>
        /// <param name="barrier">  the barrier </param>
        /// <returns> the price and derivatives </returns>
        public virtual ValueDerivatives priceAdjoint(double spot, double timeToExpiry, double costOfCarry, double rate, double lognormalVol, SimpleConstantContinuousBarrier barrier)
        {
            ArgChecker.notNull(barrier, "barrier");
            double[] derivatives = new double[6];
            bool     isKnockIn   = barrier.KnockType.KnockIn;
            bool     isDown      = barrier.BarrierType.Down;
            double   h           = barrier.BarrierLevel;

            ArgChecker.isFalse(isDown && spot <= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
            ArgChecker.isFalse(!isDown && spot >= barrier.BarrierLevel, "The Data is not consistent with an alive barrier (UP and spot>=barrier).");

            double eta            = isDown ? 1 : -1;
            double df2            = Math.Exp(-rate * timeToExpiry);
            double lognormalVolSq = lognormalVol * lognormalVol;
            double lognormalVolT  = lognormalVol * Math.Sqrt(timeToExpiry);

            if (DoubleMath.fuzzyEquals(Math.Min(timeToExpiry, lognormalVolSq), 0d, SMALL))
            {
                if (isKnockIn)
                {
                    return(ValueDerivatives.of(0d, DoubleArray.filled(6)));
                }
                double price = df2;
                derivatives[1] = -timeToExpiry * price;
                derivatives[4] = -rate * price;
                return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives)));
            }
            double mu     = (costOfCarry - 0.5 * lognormalVolSq) / lognormalVolSq;
            double lambda = Math.Sqrt(mu * mu + 2d * rate / lognormalVolSq);
            double m1     = lognormalVolT * (1d + mu);
            double x2     = Math.Log(spot / h) / lognormalVolT + m1;
            double y2     = Math.Log(h / spot) / lognormalVolT + m1;
            double z      = Math.Log(h / spot) / lognormalVolT + lambda * lognormalVolT;

            double[] eDerivFirst      = new double[6];
            double[] eDerivSecond     = new double[6];
            double[] fDerivFirst      = new double[5];
            double[] fDerivSecond     = new double[5];
            double   price            = isKnockIn ? getFAdjoint(spot, z, lognormalVolT, h, mu, lambda, eta, fDerivFirst, fDerivSecond) : getEAdjoint(spot, df2, x2, y2, lognormalVolT, h, mu, eta, eDerivFirst, eDerivSecond);
            double   zBar             = 0.0;
            double   y2Bar            = 0.0;
            double   x2Bar            = 0.0;
            double   zSqBar           = 0.0;
            double   y2SqBar          = 0.0;
            double   x2SqBar          = 0.0;
            double   zsBar            = 0.0;
            double   y2sBar           = 0.0;
            double   lambdaBar        = 0.0;
            double   muBar            = 0.0;
            double   lognormalVolTBar = 0.0;
            double   df2Bar           = 0.0;

            if (isKnockIn)
            {
                zBar             = fDerivFirst[1];
                lambdaBar        = fDerivFirst[4]; // only F has lambda dependence, which in turn is a function of mu, see muBar+= below
                muBar            = fDerivFirst[3];
                lognormalVolTBar = fDerivFirst[2];
                derivatives[0]   = fDerivFirst[0];
                zSqBar           = fDerivSecond[1];
                zsBar            = fDerivSecond[2];
                derivatives[5]   = fDerivSecond[0];
            }
            else
            {
                y2Bar            = eDerivFirst[3];
                x2Bar            = eDerivFirst[2];
                muBar            = eDerivFirst[5];
                lognormalVolTBar = eDerivFirst[4];
                df2Bar           = eDerivFirst[1];
                derivatives[0]   = eDerivFirst[0];
                y2SqBar          = eDerivSecond[2];
                x2SqBar          = eDerivSecond[1];
                y2sBar           = eDerivSecond[3];
                derivatives[5]   = eDerivSecond[0];
            }
            double dxyds = 1d / spot / lognormalVolT;
            double m1Bar = x2Bar + y2Bar;

            muBar            += +lognormalVolT * m1Bar + mu / lambda * lambdaBar;
            lognormalVolTBar += +(lambda - Math.Log(h / spot) / (lognormalVolT * lognormalVolT)) * zBar - Math.Log(h / spot) / (lognormalVolT * lognormalVolT) * y2Bar - Math.Log(spot / h) / (lognormalVolT * lognormalVolT) * x2Bar + (1 + mu) * m1Bar;
            double lognormalVolSqBar = -costOfCarry / (lognormalVolSq * lognormalVolSq) * muBar - rate / (lognormalVolSq * lognormalVolSq) / lambda * lambdaBar;

            derivatives[0] += dxyds * x2Bar - dxyds * y2Bar - dxyds * zBar;
            derivatives[1]  = -timeToExpiry * df2 * df2Bar + lambdaBar / lambda / lognormalVolSq;
            derivatives[2]  = muBar / lognormalVolSq;
            derivatives[3]  = 2d * lognormalVol * lognormalVolSqBar + Math.Sqrt(timeToExpiry) * lognormalVolTBar;
            derivatives[4]  = -rate * df2 * df2Bar + lognormalVolTBar * lognormalVolT * 0.5 / timeToExpiry;
            derivatives[5] += -dxyds * x2Bar / spot + dxyds * y2Bar / spot + dxyds * zBar / spot + dxyds * dxyds * x2SqBar + dxyds * dxyds * y2SqBar - 2d * dxyds * y2sBar + dxyds * dxyds * zSqBar - 2d * dxyds * zsBar;
            return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives)));
        }
        private double getZOverChi(double rho, double z)
        {
            // Implementation comment: To avoid numerical instability (0/0) around ATM the first order approximation is used.
            if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z))
            {
                return(1.0 - rho * z / 2.0);
            }

            double rhoStar = 1 - rho;

            if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS))
            {
                if (z < 1.0)
                {
                    return(-z / Math.Log(1.0d - z));
                }
                else
                {
                    throw new System.ArgumentException("can't handle z>=1, rho=1");
                }
            }

            double rhoHat = 1 + rho;

            if (DoubleMath.fuzzyEquals(rhoHat, 0.0, RHO_EPS_NEGATIVE))
            {
                if (z > -1)
                {
                    return(z / Math.Log(1 + z));
                }
                else if (z < -1)
                {
                    if (rhoHat == 0)
                    {
                        return(0.0);
                    }
                    double chi = Math.Log(rhoHat) - Math.Log(-(1 + z) / rhoStar);
                    return(z / chi);
                }
                else
                {
                    return(0.0);
                }
            }

            double arg;

            if (z < LARGE_NEG_Z)
            {
                arg = (rho * rho - 1) / 2 / z;   //get rounding errors due to fine balanced cancellation for very large negative z
            }
            else if (z > LARGE_POS_Z)
            {
                arg = 2 * (z - rho);
            }
            else
            {
                arg = (Math.Sqrt(1 - 2 * rho * z + z * z) + z - rho);
                //Mathematically this cannot be less than zero, but you know what computers are like.
                if (arg <= 0.0)
                {
                    return(0.0);
                }
            }

            double chi = Math.Log(arg) - Math.Log(rhoStar);

            return(z / chi);
        }
        /// <summary>
        /// Computes the implied volatility in the SABR model and its derivatives.
        /// <para>
        /// The derivatives are stored in an array with:
        /// <ul>
        /// <li>[0] derivative with respect to the forward
        /// <li>[1] derivative with respect to the strike
        /// <li>[2] derivative with respect to the alpha
        /// <li>[3] derivative with respect to the beta
        /// <li>[4] derivative with respect to the rho
        /// <li>[5] derivative with respect to the nu
        /// </ul>
        ///
        /// </para>
        /// </summary>
        /// <param name="forward">  the forward value of the underlying </param>
        /// <param name="strike">  the strike value of the option </param>
        /// <param name="timeToExpiry">  the time to expiry of the option </param>
        /// <param name="alpha">  the SABR alpha value </param>
        /// <param name="beta">  the SABR beta value </param>
        /// <param name="rho">  the SABR rho value </param>
        /// <param name="nu">  the SABR nu value </param>
        /// <returns> the volatility and associated derivatives </returns>
        public ValueDerivatives volatilityAdjoint(double forward, double strike, double timeToExpiry, double alpha, double beta, double rho, double nu)
        {
            ArgChecker.isTrue(forward > 0.0, "forward must be greater than zero");
            ArgChecker.isTrue(strike >= 0.0, "strike must be greater than zero");
            ArgChecker.isTrue(timeToExpiry >= 0.0, "timeToExpiry must be greater than zero");
            double cutoff = forward * CUTOFF_MONEYNESS;
            double k      = strike;

            if (k < cutoff)
            {
                log.info("Given strike of {} is less than cutoff at {}, therefore the strike is taken as {}", new object[] { k, cutoff, cutoff });
                k = cutoff;
            }
            double betaStar = 1 - beta;
            double rhoStar  = 1.0 - rho;

            if (alpha == 0.0)
            {
                double alphaBar;
                if (DoubleMath.fuzzyEquals(forward, k, ATM_EPS))
                {   //TODO should this is relative
                    alphaBar = (1 + (2 - 3 * rho * rho) * nu * nu / 24 * timeToExpiry) / Math.Pow(forward, betaStar);
                }
                else
                {
                    //for non-atm options the alpha sensitivity at alpha = 0 is infinite. Returning this will most likely break calibrations,
                    // so we return an arbitrary large number
                    alphaBar = 1e7;
                }
                return(ValueDerivatives.of(0d, DoubleArray.of(0, 0, alphaBar, 0, 0, 0)));
            }

            // Implementation note: Forward sweep.
            double sfK   = Math.Pow(forward * k, betaStar / 2);
            double lnrfK = Math.Log(forward / k);
            double z     = nu / alpha * sfK * lnrfK;
            double rzxz;
            double xz = 0;

            if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z))
            {
                rzxz = 1.0 - 0.5 * z * rho;   //small z expansion to z^2 terms
            }
            else
            {
                if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS))
                {
                    if (z < 1.0)
                    {
                        xz   = -Math.Log(1.0d - z);
                        rzxz = z / xz;
                    }
                    else
                    {
                        throw new System.ArgumentException("can't handle z>=1, rho=1");
                    }
                }
                else
                {
                    double arg;
                    if (z < LARGE_NEG_Z)
                    {
                        arg = (rho * rho - 1) / 2 / z;   //get rounding errors due to fine balanced cancellation for very large negative z
                    }
                    else if (z > LARGE_POS_Z)
                    {
                        arg = 2 * (z - rho);
                    }
                    else
                    {
                        arg = (Math.Sqrt(1 - 2 * rho * z + z * z) + z - rho);
                    }
                    if (arg <= 0.0)
                    {     //Mathematically this cannot be less than zero, but you know what computers are like.
                        rzxz = 0.0;
                    }
                    else
                    {
                        xz   = Math.Log(arg / (1 - rho));
                        rzxz = z / xz;
                    }
                }
            }
            double sf1        = sfK * (1 + betaStar * betaStar / 24 * (lnrfK * lnrfK) + Math.Pow(betaStar, 4) / 1920 * Math.Pow(lnrfK, 4));
            double sf2        = (1 + (Math.Pow(betaStar * alpha / sfK, 2) / 24 + (rho * beta * nu * alpha) / (4 * sfK) + (2 - 3 * rho * rho) * nu * nu / 24) * timeToExpiry);
            double volatility = Math.Max(MIN_VOL, alpha / sf1 * rzxz * sf2);

            // Implementation note: Backward sweep.
            double vBar    = 1;
            double sf2Bar  = alpha / sf1 * rzxz * vBar;
            double sf1Bar  = -alpha / (sf1 * sf1) * rzxz * sf2 * vBar;
            double rzxzBar = alpha / sf1 * sf2 * vBar;
            double zBar;
            double xzBar = 0.0;

            if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z))
            {
                zBar = -rho / 2 * rzxzBar;
            }
            else
            {
                if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS))
                {
                    if (z < 1.0)
                    {
                        xzBar = -z / (xz * xz) * rzxzBar;
                        zBar  = 1.0d / xz * rzxzBar + 1.0d / (1.0d - z) * xzBar;
                    }
                    else
                    {
                        throw new System.ArgumentException("can't handle z>=1, rho=1");
                    }
                }
                else
                {
                    if (z < LARGE_NEG_Z)
                    {
                        zBar = 1 / xz * rzxzBar + xzBar / (xz * xz) * rzxzBar;
                    }
                    else if (z > LARGE_POS_Z)
                    {
                        zBar = 1 / xz * rzxzBar - xzBar / (xz * xz) * rzxzBar;
                    }
                    else
                    {
                        xzBar = -z / (xz * xz) * rzxzBar;
                        zBar  = 1 / xz * rzxzBar + 1 / ((Math.Sqrt(1 - 2 * rho * z + z * z) + z - rho)) * (0.5 * Math.Pow(1 - 2 * rho * z + z * z, -0.5) * (-2 * rho + 2 * z) + 1) * xzBar;
                    }
                }
            }

            double lnrfKBar   = sfK * (betaStar * betaStar / 12 * lnrfK + Math.Pow(betaStar, 4) / 1920 * 4 * Math.Pow(lnrfK, 3)) * sf1Bar + nu / alpha * sfK * zBar;
            double sfKBar     = nu / alpha * lnrfK * zBar + sf1 / sfK * sf1Bar - (Math.Pow(betaStar * alpha, 2) / Math.Pow(sfK, 3) / 12 + (rho * beta * nu * alpha) / 4 / (sfK * sfK)) * timeToExpiry * sf2Bar;
            double strikeBar  = -1 / k * lnrfKBar + betaStar * sfK / (2 * k) * sfKBar;
            double forwardBar = 1 / forward * lnrfKBar + betaStar * sfK / (2 * forward) * sfKBar;
            double nuBar      = 1 / alpha * sfK * lnrfK * zBar + ((rho * beta * alpha) / (4 * sfK) + (2 - 3 * rho * rho) * nu / 12) * timeToExpiry * sf2Bar;

            double rhoBar;

            if (Math.Abs(forward - k) < ATM_EPS)
            {
                rhoBar = -z / 2 * rzxzBar;
            }
            else
            {
                if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS))
                {
                    if (z >= 1)
                    {
                        if (rhoStar == 0.0)
                        {
                            rhoBar = double.NegativeInfinity;     //the derivative at rho = 1 is infinite  - this sets it to some arbitrary large number
                        }
                        else
                        {
                            rhoBar = xzBar * (1.0 / rhoStar + (0.5 - z) / (z - 1.0) / (z - 1.0));
                        }
                    }
                    else
                    {
                        rhoBar = (0.5 * Math.Pow(z / (1 - z), 2) + 0.25 * (z - 4.0) * Math.Pow(z / (1.0 - z), 3) / (1.0 - z) * rhoStar) * xzBar;
                    }
                }
                else
                {
                    rhoBar = (1 / (Math.Sqrt(1 - 2 * rho * z + z * z) + z - rho) * (-Math.Pow(1 - 2 * rho * z + z * z, -0.5) * z - 1) + 1 / rhoStar) * xzBar;
                }
            }
            rhoBar += ((beta * nu * alpha) / (4 * sfK) - rho * nu * nu / 4) * timeToExpiry * sf2Bar;

            double alphaBar = -nu / (alpha * alpha) * sfK * lnrfK * zBar + ((betaStar * alpha / sfK) * (betaStar / sfK) / 12 + (rho * beta * nu) / (4 * sfK)) * timeToExpiry * sf2Bar + 1 / sf1 * rzxz * sf2 * vBar;
            double betaBar  = -0.5 * Math.Log(forward * k) * sfK * sfKBar - sfK * (betaStar / 12 * (lnrfK * lnrfK) + Math.Pow(betaStar, 3) / 480 * Math.Pow(lnrfK, 4)) * sf1Bar + (-betaStar * alpha * alpha / sfK / sfK / 12 + rho * nu * alpha / 4 / sfK) * timeToExpiry * sf2Bar;

            return(ValueDerivatives.of(volatility, DoubleArray.of(forwardBar, strikeBar, alphaBar, betaBar, rhoBar, nuBar)));
        }
        //-------------------------------------------------------------------------
        public override double?apply(double?x)
        {
            ArgChecker.inRangeInclusive(x, 0d, 1d, "x");
            double pp, p, t, h, w, lnA, lnB, u, a1 = _a - 1;
            double b1 = _b - 1;

            if (_a >= 1 && _b >= 1)
            {
                pp = x < 0.5 ? x.Value : 1 - x;
                t  = Math.Sqrt(-2 * Math.Log(pp));
                p  = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t;
                if (p < 0.5)
                {
                    p *= -1;
                }
                a1 = (Math.Sqrt(p) - 3.0) / 6.0;
                double tempA = 1.0 / (2 * _a - 1);
                double tempB = 1.0 / (2 * _b - 1);
                h = 2.0 / (tempA + tempB);
                w = p * Math.Sqrt(a1 + h) / h - (tempB - tempA) * (a1 + 5.0 / 6 - 2.0 / (3 * h));
                p = _a / (_a + _b + Math.Exp(2 * w));
            }
            else
            {
                lnA = Math.Log(_a / (_a + _b));
                lnB = Math.Log(_b / (_a + _b));
                t   = Math.Exp(_a * lnA) / _a;
                u   = Math.Exp(_b * lnB) / _b;
                w   = t + u;
                if (x.Value < t / w)
                {
                    p = Math.Pow(_a * w * x, 1.0 / _a);
                }
                else
                {
                    p = 1 - Math.Pow(_b * w * (1 - x), 1.0 / _b);
                }
            }
            double afac = -_lnGamma.apply(_a) - _lnGamma.apply(_b) + _lnGamma.apply(_a + _b);
            double error;

            for (int j = 0; j < 10; j++)
            {
                if (DoubleMath.fuzzyEquals(p, 0d, 1e-16) || DoubleMath.fuzzyEquals(p, (double)1, 1e-16))
                {
                    throw new MathException("a or b too small for accurate evaluation");
                }
                error = _beta.apply(p) - x;
                t     = Math.Exp(a1 * Math.Log(p) + b1 * Math.Log(1 - p) + afac);
                u     = error / t;
                t     = u / (1 - 0.5 * Math.Min(1, u * (a1 / p - b1 / (1 - p))));
                p    -= t;
                if (p <= 0)
                {
                    p = 0.5 * (p + t);
                }
                if (p >= 1)
                {
                    p = 0.5 * (p + t + 1);
                }
                if (Math.Abs(t) < EPS * p && j > 0)
                {
                    break;
                }
            }
            return(p);
        }