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)); }
/// <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); } }
/// <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); }
//------------------------------------------------------------------------- /// <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)); }
//------------------------------------------------------------------------- /// <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); }
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) }); }
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)); }
/// <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); }
/// <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); }
/// <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); }