/// <summary> /// Greeks against finite difference approximation. /// </summary> public virtual void greekfdTest() { foreach (SimpleConstantContinuousBarrier barrier in BARRIERS) { ValueDerivatives computed = PRICER.priceAdjoint(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double spotUp = PRICER.price(SPOT + EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double spotDw = PRICER.price(SPOT - EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double rateUp = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM + EPS_FD, VOLATILITY, barrier); double rateDw = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM - EPS_FD, VOLATILITY, barrier); double costUp = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY + EPS_FD, RATE_DOM, VOLATILITY, barrier); double costDw = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY - EPS_FD, RATE_DOM, VOLATILITY, barrier); double volUp = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY + EPS_FD, barrier); double volDw = PRICER.price(SPOT, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY - EPS_FD, barrier); double timeUp = PRICER.price(SPOT, EXPIRY_TIME + EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); double timeDw = PRICER.price(SPOT, EXPIRY_TIME - EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); ValueDerivatives spotUp1 = PRICER.priceAdjoint(SPOT + EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); ValueDerivatives spotDw1 = PRICER.priceAdjoint(SPOT - EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, barrier); assertEquals(computed.getDerivative(0), 0.5 * (spotUp - spotDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(1), 0.5 * (rateUp - rateDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(2), 0.5 * (costUp - costDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(3), 0.5 * (volUp - volDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(4), 0.5 * (timeUp - timeDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(5), 0.5 * (spotUp1.getDerivative(0) - spotDw1.getDerivative(0)) / EPS_FD, EPS_FD); } }
protected internal override double doFirstDerivative(double xValue) { ArgChecker.isTrue(Math.Abs(xValue) > SMALL, "magnitude of xValue must not be small"); ValueDerivatives resValue = FUNCTION.evaluateAndDifferentiate(poly, xValue); return(-resValue.Value / (xValue * xValue) + resValue.getDerivative(0) / xValue); }
/// <summary> /// Computes the option price derivative with respect to the SABR parameters. /// <para> /// The price is SABR below the cut-off strike and extrapolated beyond. /// /// </para> /// </summary> /// <param name="strike"> the strike of the option </param> /// <param name="putCall"> whether the option is put or call </param> /// <returns> the option and its derivative </returns> public ValueDerivatives priceAdjointSabr(double strike, PutCall putCall) { double[] priceDerivativeSabr = new double[4]; double price; if (strike <= cutOffStrike) { // Uses Hagan et al SABR function. ValueDerivatives volatilityA = sabrFunction.volatilityAdjoint(forward, strike, timeToExpiry, sabrData); ValueDerivatives pA = BlackFormulaRepository.priceAdjoint(forward, strike, timeToExpiry, volatilityA.Value, putCall == PutCall.CALL); price = pA.Value; for (int loopparam = 0; loopparam < 4; loopparam++) { priceDerivativeSabr[loopparam] = pA.getDerivative(3) * volatilityA.getDerivative(loopparam + 2); } } else { // Uses extrapolation for call. if (parameterDerivativeSabr == null) { parameterDerivativeSabr = computesParametersDerivativeSabr(); // Derivatives computed only once and only when required } double f = extrapolation(strike); double fDa = f; double fDb = f / strike; double fDc = fDb / strike; price = putCall.Call ? f : f - forward + strike; // Put by call/put parity for (int loopparam = 0; loopparam < 4; loopparam++) { priceDerivativeSabr[loopparam] = fDa * parameterDerivativeSabr[loopparam][0] + fDb * parameterDerivativeSabr[loopparam][1] + fDc * parameterDerivativeSabr[loopparam][2]; } } return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(priceDerivativeSabr))); }
public virtual void swapRateDa() { double shift = 1.0E-8; double x = 0.0; ValueDerivatives computed = MODEL.swapRateDaf1(x, DCF_FIXED, ALPHA_FIXED, DCF_IBOR, ALPHA_IBOR); double swapRateComputed = computed.Value; double[] dafComputed = computed.Derivatives.toArray(); double swapRateExpected = MODEL.swapRate(x, DCF_FIXED, ALPHA_FIXED, DCF_IBOR, ALPHA_IBOR); assertEquals(swapRateComputed, swapRateExpected, TOLERANCE_RATE); double[] dafExpected = new double[ALPHA_FIXED.size()]; for (int loopcf = 0; loopcf < ALPHA_FIXED.size(); loopcf++) { double[] afBumped = ALPHA_FIXED.toArray(); afBumped[loopcf] += shift; double swapRatePlus = MODEL.swapRate(x, DCF_FIXED, DoubleArray.copyOf(afBumped), DCF_IBOR, ALPHA_IBOR); afBumped[loopcf] -= 2 * shift; double swapRateMinus = MODEL.swapRate(x, DCF_FIXED, DoubleArray.copyOf(afBumped), DCF_IBOR, ALPHA_IBOR); dafExpected[loopcf] = (swapRatePlus - swapRateMinus) / (2 * shift); } assertTrue(DoubleArrayMath.fuzzyEquals(dafExpected, dafComputed, TOLERANCE_RATE_DELTA)); double[] daiExpected = new double[DCF_IBOR.size()]; for (int loopcf = 0; loopcf < DCF_IBOR.size(); loopcf++) { double[] aiBumped = ALPHA_IBOR.toArray(); aiBumped[loopcf] += shift; double swapRatePlus = MODEL.swapRate(x, DCF_FIXED, ALPHA_FIXED, DCF_IBOR, DoubleArray.copyOf(aiBumped)); aiBumped[loopcf] -= 2 * shift; double swapRateMinus = MODEL.swapRate(x, DCF_FIXED, ALPHA_FIXED, DCF_IBOR, DoubleArray.copyOf(aiBumped)); daiExpected[loopcf] = (swapRatePlus - swapRateMinus) / (2 * shift); } double[] daiComputed = MODEL.swapRateDai1(x, DCF_FIXED, ALPHA_FIXED, DCF_IBOR, ALPHA_IBOR).Derivatives.toArray(); assertTrue(DoubleArrayMath.fuzzyEquals(daiExpected, daiComputed, TOLERANCE_RATE_DELTA)); }
public virtual DeformedSurface localVolatilityFromPrice(Surface callPriceSurface, double spot, System.Func <double, double> interestRate, System.Func <double, double> dividendRate) { System.Func <DoublesPair, ValueDerivatives> func = (DoublesPair x) => { double t = x.First; double k = x.Second; double r = interestRate(t); double q = dividendRate(t); double price = callPriceSurface.zValue(t, k); DoubleArray priceSensi = callPriceSurface.zValueParameterSensitivity(t, k).Sensitivity; double divT = FIRST_DERIV.differentiate(u => callPriceSurface.zValue(u, k)).apply(t); DoubleArray divTSensi = FIRST_DERIV_SENSI.differentiate(u => callPriceSurface.zValueParameterSensitivity(u.get(0), k).Sensitivity).apply(DoubleArray.of(t)).column(0); double divK = FIRST_DERIV.differentiate(l => callPriceSurface.zValue(t, l)).apply(k); DoubleArray divKSensi = FIRST_DERIV_SENSI.differentiate(l => callPriceSurface.zValueParameterSensitivity(t, l.get(0)).Sensitivity).apply(DoubleArray.of(k)).column(0); double divK2 = SECOND_DERIV.differentiate(l => callPriceSurface.zValue(t, l)).apply(k); DoubleArray divK2Sensi = SECOND_DERIV_SENSI.differentiateNoCross(l => callPriceSurface.zValueParameterSensitivity(t, l.get(0)).Sensitivity).apply(DoubleArray.of(k)).column(0); double var = 2d * (divT + q * price + (r - q) * k * divK) / (k * k * divK2); if (var < 0d) { throw new System.ArgumentException("Negative variance"); } double localVol = Math.Sqrt(var); double factor = 1d / (localVol * k * k * divK2); DoubleArray localVolSensi = divTSensi.multipliedBy(factor).plus(divKSensi.multipliedBy((r - q) * k * factor)).plus(priceSensi.multipliedBy(q * factor)).plus(divK2Sensi.multipliedBy(-0.5 * localVol / divK2)); return(ValueDerivatives.of(localVol, localVolSensi)); }; SurfaceMetadata metadata = DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.STRIKE).zValueType(ValueType.LOCAL_VOLATILITY).surfaceName(SurfaceName.of("localVol_" + callPriceSurface.Name)).build(); return(DeformedSurface.of(metadata, callPriceSurface, func)); }
// Test getDelta, getGamma and getVega public virtual void greeksTest() { double tol = 1.0e-12; double eps = 1.0e-5; EuropeanVanillaOption[] options = new EuropeanVanillaOption[] { ITM_CALL, ITM_PUT, OTM_CALL, OTM_PUT, ATM_CALL, ATM_PUT }; foreach (EuropeanVanillaOption option in options) { // consistency with getPriceFunction for first order derivatives ValueDerivatives price = FUNCTION.getPriceAdjoint(option, VOL_DATA); double delta = FUNCTION.getDelta(option, VOL_DATA); double vega = FUNCTION.getVega(option, VOL_DATA); assertEquals(price.getDerivative(0), delta, tol); assertEquals(price.getDerivative(1), vega, tol); // testing second order derivative against finite difference approximation NormalFunctionData dataUp = NormalFunctionData.of(F + eps, DF, SIGMA); NormalFunctionData dataDw = NormalFunctionData.of(F - eps, DF, SIGMA); double deltaUp = FUNCTION.getDelta(option, dataUp); double deltaDw = FUNCTION.getDelta(option, dataDw); double @ref = 0.5 * (deltaUp - deltaDw) / eps; double gamma = FUNCTION.getGamma(option, VOL_DATA); assertEquals(gamma, @ref, eps); EuropeanVanillaOption optionUp = EuropeanVanillaOption.of(option.Strike, T + eps, option.PutCall); EuropeanVanillaOption optionDw = EuropeanVanillaOption.of(option.Strike, T - eps, option.PutCall); double priceTimeUp = FUNCTION.getPriceFunction(optionUp).apply(VOL_DATA); double priceTimeDw = FUNCTION.getPriceFunction(optionDw).apply(VOL_DATA); @ref = -0.5 * (priceTimeUp - priceTimeDw) / eps; double theta = FUNCTION.getTheta(option, VOL_DATA); assertEquals(theta, @ref, eps); } }
/// <summary> /// Test the future convexity adjustment factor v a hard-coded value. /// </summary> public virtual void futureConvexityFactor() { LocalDate SPOT_DATE = LocalDate.of(2012, 9, 19); LocalDate LAST_TRADING_DATE = EURIBOR3M.calculateFixingFromEffective(SPOT_DATE, REF_DATA); LocalDate REFERENCE_DATE = LocalDate.of(2010, 8, 18); double tradeLastTime = DayCounts.ACT_ACT_ISDA.relativeYearFraction(REFERENCE_DATE, LAST_TRADING_DATE); double fixStartTime = DayCounts.ACT_ACT_ISDA.relativeYearFraction(REFERENCE_DATE, SPOT_DATE); double fixEndTime = DayCounts.ACT_ACT_ISDA.relativeYearFraction(REFERENCE_DATE, EURIBOR3M.calculateMaturityFromEffective(SPOT_DATE, REF_DATA)); double factor = MODEL.futuresConvexityFactor(MODEL_PARAMETERS, tradeLastTime, fixStartTime, fixEndTime); double expectedFactor = 1.000079130767980; assertEquals(expectedFactor, factor, TOLERANCE_RATE); // Derivative with respect to volatility parameters int nbSigma = MODEL_PARAMETERS.Volatility.size(); ValueDerivatives factorDeriv = MODEL.futuresConvexityFactorAdjoint(MODEL_PARAMETERS, tradeLastTime, fixStartTime, fixEndTime); double factor2 = factorDeriv.Value; double[] sigmaBar = factorDeriv.Derivatives.toArray(); assertEquals(factor, factor2, TOLERANCE_RATE); double[] sigmaBarExpected = new double[nbSigma]; double shift = 1E-6; for (int loops = 0; loops < nbSigma; loops++) { double[] volBumped = VOLATILITY.toArray(); volBumped[loops] += shift; HullWhiteOneFactorPiecewiseConstantParameters parametersBumped = HullWhiteOneFactorPiecewiseConstantParameters.of(MEAN_REVERSION, DoubleArray.copyOf(volBumped), VOLATILITY_TIME); double factorPlus = MODEL.futuresConvexityFactor(parametersBumped, tradeLastTime, fixStartTime, fixEndTime); volBumped[loops] -= 2 * shift; parametersBumped = HullWhiteOneFactorPiecewiseConstantParameters.of(MEAN_REVERSION, DoubleArray.copyOf(volBumped), VOLATILITY_TIME); double factorMinus = MODEL.futuresConvexityFactor(parametersBumped, tradeLastTime, fixStartTime, fixEndTime); sigmaBarExpected[loops] = (factorPlus - factorMinus) / (2 * shift); assertEquals(sigmaBarExpected[loops], sigmaBar[loops], TOLERANCE_RATE); } }
//------------------------------------------------------------------------- /// <summary> /// Calculates the present value sensitivity of the swaption product to the rate curves. /// <para> /// The present value sensitivity is computed in a "sticky model parameter" style, i.e. the sensitivity to the /// curve nodes with the SABR model parameters unchanged. This sensitivity does not include a potential /// re-calibration of the model parameters to the raw market data. /// /// </para> /// </summary> /// <param name="swaption"> the swaption product </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="swaptionVolatilities"> the volatilities </param> /// <returns> the point sensitivity to the rate curves </returns> public virtual PointSensitivityBuilder presentValueSensitivityRatesStickyModel(ResolvedSwaption swaption, RatesProvider ratesProvider, SabrSwaptionVolatilities swaptionVolatilities) { validate(swaption, ratesProvider, swaptionVolatilities); ZonedDateTime expiryDateTime = swaption.Expiry; double expiry = swaptionVolatilities.relativeTime(expiryDateTime); ResolvedSwap underlying = swaption.Underlying; ResolvedSwapLeg fixedLeg = this.fixedLeg(underlying); if (expiry < 0d) { // Option has expired already return(PointSensitivityBuilder.none()); } double forward = SwapPricer.parRate(underlying, ratesProvider); double pvbp = SwapPricer.LegPricer.pvbp(fixedLeg, ratesProvider); double strike = SwapPricer.LegPricer.couponEquivalent(fixedLeg, ratesProvider, pvbp); double tenor = swaptionVolatilities.tenor(fixedLeg.StartDate, fixedLeg.EndDate); double shift = swaptionVolatilities.shift(expiry, tenor); ValueDerivatives volatilityAdj = swaptionVolatilities.volatilityAdjoint(expiry, tenor, strike, forward); bool isCall = fixedLeg.PayReceive.Pay; // Payer at strike is exercise when rate > strike, i.e. call on rate // Backward sweep PointSensitivityBuilder pvbpDr = SwapPricer.LegPricer.pvbpSensitivity(fixedLeg, ratesProvider); PointSensitivityBuilder forwardDr = SwapPricer.parRateSensitivity(underlying, ratesProvider); double shiftedForward = forward + shift; double shiftedStrike = strike + shift; double price = BlackFormulaRepository.price(shiftedForward, shiftedStrike, expiry, volatilityAdj.Value, isCall); double delta = BlackFormulaRepository.delta(shiftedForward, shiftedStrike, expiry, volatilityAdj.Value, isCall); double vega = BlackFormulaRepository.vega(shiftedForward, shiftedStrike, expiry, volatilityAdj.Value); double sign = swaption.LongShort.sign(); return(pvbpDr.multipliedBy(price * sign * Math.Sign(pvbp)).combinedWith(forwardDr.multipliedBy((delta + vega * volatilityAdj.getDerivative(0)) * Math.Abs(pvbp) * sign))); }
private void testDerivatives(double strike, bool isCall, SimpleConstantContinuousBarrier barrier) { ValueDerivatives computed = BARRIER_PRICER.priceAdjoint(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double spotUp = BARRIER_PRICER.price(SPOT + EPS_FD, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double spotDw = BARRIER_PRICER.price(SPOT - EPS_FD, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double strikeUp = BARRIER_PRICER.price(SPOT, strike + EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double strikeDw = BARRIER_PRICER.price(SPOT, strike - EPS_FD, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double rateUp = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM + EPS_FD, VOLATILITY, isCall, barrier); double rateDw = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM - EPS_FD, VOLATILITY, isCall, barrier); double costUp = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY + EPS_FD, RATE_DOM, VOLATILITY, isCall, barrier); double costDw = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY - EPS_FD, RATE_DOM, VOLATILITY, isCall, barrier); double volUp = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY + EPS_FD, isCall, barrier); double volDw = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY - EPS_FD, isCall, barrier); double timeUp = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME + EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); double timeDw = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME - EPS_FD, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); ValueDerivatives spotUp1 = BARRIER_PRICER.priceAdjoint(SPOT + EPS_FD, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); ValueDerivatives spotDw1 = BARRIER_PRICER.priceAdjoint(SPOT - EPS_FD, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, isCall, barrier); assertEquals(computed.getDerivative(0), 0.5 * (spotUp - spotDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(1), 0.5 * (strikeUp - strikeDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(2), 0.5 * (rateUp - rateDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(3), 0.5 * (costUp - costDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(4), 0.5 * (volUp - volDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(5), 0.5 * (timeUp - timeDw) / EPS_FD, EPS_FD); assertEquals(computed.getDerivative(6), 0.5 * (spotUp1.getDerivative(0) - spotDw1.getDerivative(0)) / EPS_FD, EPS_FD); }
/// <summary> /// Compute the implied volatility using an approximate explicit transformation formula and its derivative /// with respect to the input Black volatility. /// <para> /// Reference: Hagan, P. S. Volatility conversion calculator. Technical report, Bloomberg. /// /// </para> /// </summary> /// <param name="forward"> the forward rate/price </param> /// <param name="strike"> the option strike </param> /// <param name="timeToExpiry"> the option time to maturity </param> /// <param name="blackVolatility"> the Black implied volatility </param> /// <returns> the implied volatility and its derivative </returns> public static ValueDerivatives impliedVolatilityFromBlackApproximatedAdjoint(double forward, double strike, double timeToExpiry, double blackVolatility) { ArgChecker.isTrue(strike > 0, "strike must be strictly positive"); ArgChecker.isTrue(forward > 0, "strike must be strictly positive"); double lnFK = Math.Log(forward / strike); double s2t = blackVolatility * blackVolatility * timeToExpiry; if (Math.Abs((forward - strike) / strike) < ATM_LIMIT) { double factor1 = Math.Sqrt(forward * strike); double factor2 = (1.0d + lnFK * lnFK / 24.0d) / (1.0d + s2t / 24.0d + s2t * s2t / 5670.0d); double normalVol = blackVolatility * factor1 * factor2; // Backward sweep double blackVolatilityBar = factor1 * factor2; double factor2Bar = blackVolatility * factor1; double s2tBar = -(1.0d + lnFK * lnFK / 24.0d) / ((1.0d + s2t / 24.0d + s2t * s2t / 5670.0d) * (1.0d + s2t / 24.0d + s2t * s2t / 5670.0d)) * (1.0d / 24.0d + s2t / 2835.0d) * factor2Bar; blackVolatilityBar += 2.0d * blackVolatility * timeToExpiry * s2tBar; return(ValueDerivatives.of(normalVol, DoubleArray.of(blackVolatilityBar))); } double factor1 = (forward - strike) / lnFK; double factor2 = 1.0d / (1.0d + (1.0d - lnFK * lnFK / 120.0d) / 24.0d * s2t + s2t * s2t / 5670.0d); double normalVol = blackVolatility * factor1 * factor2; // Backward sweep double blackVolatilityBar = factor1 * factor2; double factor2Bar = blackVolatility * factor1; double s2tBar = -factor2 * factor2 * ((1.0d - lnFK * lnFK / 120.0d) / 24.0d + s2t / 2835.0d) * factor2Bar; blackVolatilityBar += 2.0d * blackVolatility * timeToExpiry * s2tBar; return(ValueDerivatives.of(normalVol, DoubleArray.of(blackVolatilityBar))); }
/// <summary> /// Computes the conventional cash annuity for a given yield and its first two derivatives with respect to the yield. /// </summary> /// <param name="nbPaymentsPerYear"> the number of payment per year </param> /// <param name="nbPeriods"> the total number of periods </param> /// <param name="yield"> the yield </param> /// <returns> the cash annuity and its first two derivatives </returns> public virtual ValueDerivatives annuityCash2(int nbPaymentsPerYear, int nbPeriods, double yield) { double tau = 1d / nbPaymentsPerYear; if (Math.Abs(yield) > MIN_YIELD) { double yieldPerPeriod = yield * tau; double dfEnd = Math.Pow(1d + yieldPerPeriod, -nbPeriods); double annuity = (1d - dfEnd) / yield; double derivative1 = -annuity / yield; derivative1 += tau * nbPeriods * dfEnd / ((1d + yieldPerPeriod) * yield); double derivative2 = -2 * derivative1 / yield; derivative2 -= tau * tau * nbPeriods * (nbPeriods + 1) * dfEnd / ((1d + yieldPerPeriod) * (1d + yieldPerPeriod) * yield); return(ValueDerivatives.of(annuity, DoubleArray.of(derivative1, derivative2))); } double annuity = 0.0d; double derivative1 = 0.0d; double derivative2 = 0.0d; double periodFactor = 1.0d / (1.0d + yield * tau); double multiPeriodFactor = periodFactor; for (int i = 0; i < nbPeriods; i++) { annuity += multiPeriodFactor; multiPeriodFactor *= periodFactor; derivative1 += -(i + 1) * multiPeriodFactor; derivative2 += (i + 1) * (i + 2) * multiPeriodFactor * periodFactor; } annuity *= tau; derivative1 *= tau * tau; derivative2 *= tau * tau * tau; return(ValueDerivatives.of(annuity, DoubleArray.of(derivative1, derivative2))); }
//------------------------------------------------------------------------- /// <summary> /// Tests of performance. "enabled = false" for the standard testing. /// </summary> //JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes: //ORIGINAL LINE: @Test(enabled = false) public void performanceAlphaAdjoint() public virtual void performanceAlphaAdjoint() { double expiry1 = 0.25; double expiry2 = 2.25; double numeraire = 10.0; double maturity = 9.0; int nbVolatility = VOLATILITY.size(); long startTime, endTime; int nbTest = 100000; double alpha = 0.0; startTime = DateTimeHelper.CurrentUnixTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { alpha = MODEL.alpha(MODEL_PARAMETERS, expiry1, expiry2, numeraire, maturity); } endTime = DateTimeHelper.CurrentUnixTimeMillis(); Console.WriteLine(nbTest + " alpha Hull-White: " + (endTime - startTime) + " ms"); startTime = DateTimeHelper.CurrentUnixTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { ValueDerivatives computed = MODEL.alphaAdjoint(MODEL_PARAMETERS, expiry1, expiry2, numeraire, maturity); alpha = computed.Value; } endTime = DateTimeHelper.CurrentUnixTimeMillis(); Console.WriteLine(nbTest + " alpha Hull-White adjoint (value+" + nbVolatility + " derivatives): " + (endTime - startTime) + " ms"); // Performance note: value: 31-Aug-11: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 75 ms for 1000000 swaptions. // Performance note: value+derivatives: 31-Aug-11: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 100 ms for 1000000 swaptions. Console.WriteLine("Alpha: " + alpha); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the present value sensitivity of the Ibor caplet/floorlet to the rate curves. /// <para> /// The present value sensitivity is computed in a "sticky model parameter" style, i.e. the sensitivity to the /// curve nodes with the SABR model parameters unchanged. This sensitivity does not include a potential /// re-calibration of the model parameters to the raw market data. /// /// </para> /// </summary> /// <param name="period"> the Ibor caplet/floorlet period </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="volatilities"> the volatilities </param> /// <returns> the point sensitivity to the rate curves </returns> public virtual PointSensitivityBuilder presentValueSensitivityRatesStickyModel(IborCapletFloorletPeriod period, RatesProvider ratesProvider, SabrIborCapletFloorletVolatilities volatilities) { Currency currency = period.Currency; if (ratesProvider.ValuationDate.isAfter(period.PaymentDate)) { return(PointSensitivityBuilder.none()); } double expiry = volatilities.relativeTime(period.FixingDateTime); PutCall putCall = period.PutCall; double strike = period.Strike; double indexRate = ratesProvider.iborIndexRates(period.Index).rate(period.IborRate.Observation); PointSensitivityBuilder dfSensi = ratesProvider.discountFactors(currency).zeroRatePointSensitivity(period.PaymentDate); double factor = period.Notional * period.YearFraction; if (expiry < 0d) { // option expired already, but not yet paid double sign = putCall.Call ? 1d : -1d; double payoff = Math.Max(sign * (indexRate - strike), 0d); return(dfSensi.multipliedBy(payoff * factor)); } ValueDerivatives volatilityAdj = volatilities.volatilityAdjoint(expiry, strike, indexRate); PointSensitivityBuilder indexRateSensiSensi = ratesProvider.iborIndexRates(period.Index).ratePointSensitivity(period.IborRate.Observation); double df = ratesProvider.discountFactor(currency, period.PaymentDate); double fwdPv = factor * volatilities.price(expiry, putCall, strike, indexRate, volatilityAdj.Value); double fwdDelta = factor * volatilities.priceDelta(expiry, putCall, strike, indexRate, volatilityAdj.Value); double fwdVega = factor * volatilities.priceVega(expiry, putCall, strike, indexRate, volatilityAdj.Value); return(dfSensi.multipliedBy(fwdPv).combinedWith(indexRateSensiSensi.multipliedBy(fwdDelta * df + fwdVega * volatilityAdj.getDerivative(0) * df))); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the present value sensitivity of the swaption to the rate curves. /// <para> /// The present value sensitivity is computed in a "sticky strike" style, i.e. the sensitivity to the /// curve nodes with the volatility at the swaption strike unchanged. This sensitivity does not include a potential /// change of volatility due to the implicit change of forward rate or moneyness. /// /// </para> /// </summary> /// <param name="swaption"> the swaption </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="swaptionVolatilities"> the volatilities </param> /// <returns> the point sensitivity to the rate curves </returns> public virtual PointSensitivityBuilder presentValueSensitivityRatesStickyStrike(ResolvedSwaption swaption, RatesProvider ratesProvider, SwaptionVolatilities swaptionVolatilities) { validate(swaption, ratesProvider, swaptionVolatilities); double expiry = swaptionVolatilities.relativeTime(swaption.Expiry); ResolvedSwap underlying = swaption.Underlying; ResolvedSwapLeg fixedLeg = this.fixedLeg(underlying); if (expiry < 0d) { // Option has expired already return(PointSensitivityBuilder.none()); } double forward = SwapPricer.parRate(underlying, ratesProvider); ValueDerivatives annuityDerivative = SwapPricer.LegPricer.annuityCashDerivative(fixedLeg, forward); double annuityCash = annuityDerivative.Value; double annuityCashDr = annuityDerivative.getDerivative(0); LocalDate settlementDate = ((CashSwaptionSettlement)swaption.SwaptionSettlement).SettlementDate; double discountSettle = ratesProvider.discountFactor(fixedLeg.Currency, settlementDate); double strike = calculateStrike(fixedLeg); double tenor = swaptionVolatilities.tenor(fixedLeg.StartDate, fixedLeg.EndDate); double volatility = swaptionVolatilities.volatility(expiry, tenor, strike, forward); PutCall putCall = PutCall.ofPut(fixedLeg.PayReceive.Receive); double price = swaptionVolatilities.price(expiry, tenor, putCall, strike, forward, volatility); double delta = swaptionVolatilities.priceDelta(expiry, tenor, putCall, strike, forward, volatility); // Backward sweep PointSensitivityBuilder forwardSensi = SwapPricer.parRateSensitivity(underlying, ratesProvider); PointSensitivityBuilder discountSettleSensi = ratesProvider.discountFactors(fixedLeg.Currency).zeroRatePointSensitivity(settlementDate); double sign = swaption.LongShort.sign(); return(forwardSensi.multipliedBy(sign * discountSettle * (annuityCash * delta + annuityCashDr * price)).combinedWith(discountSettleSensi.multipliedBy(sign * annuityCash * price))); }
/// <summary> /// Computes the option price derivative with respect to the forward. /// <para> /// The price is SABR below the cut-off strike and extrapolated beyond. /// /// </para> /// </summary> /// <param name="strike"> the strike of the option </param> /// <param name="putCall"> whether the option is put or call </param> /// <returns> the option price derivative </returns> public double priceDerivativeForward(double strike, PutCall putCall) { // Uses Hagan et al SABR function. if (strike <= cutOffStrike) { ValueDerivatives volatilityA = sabrFunction.volatilityAdjoint(forward, strike, timeToExpiry, sabrData); ValueDerivatives pA = BlackFormulaRepository.priceAdjoint(forward, strike, timeToExpiry, volatilityA.Value, putCall == PutCall.CALL); return(pA.getDerivative(0) + pA.getDerivative(3) * volatilityA.getDerivative(0)); } // Uses extrapolation for call. if (parameterDerivativeForward == null) { parameterDerivativeForward = computesParametersDerivativeForward(); } double f = extrapolation(strike); double fDa = f; double fDb = f / strike; double fDc = fDb / strike; double priceDerivative = fDa * parameterDerivativeForward[0] + fDb * parameterDerivativeForward[1] + fDc * parameterDerivativeForward[2]; if (putCall.Put) { // Put by call/put parity priceDerivative -= 1; } return(priceDerivative); }
/// <summary> /// Calculates the future convexity factor and its derivatives with respect to the model volatilities. /// <para> /// The factor is called gamma in the reference: /// Henrard, M. "The Irony in the derivatives discounting Part II: the crisis", Wilmott Journal, 2010, 2, 301-316 /// /// </para> /// </summary> /// <param name="data"> the Hull-White model parameters </param> /// <param name="t0"> the expiry time </param> /// <param name="t1"> the first reference time </param> /// <param name="t2"> the second reference time </param> /// <returns> the factor and drivatives </returns> public ValueDerivatives futuresConvexityFactorAdjoint(HullWhiteOneFactorPiecewiseConstantParameters data, double t0, double t1, double t2) { double factor1 = Math.Exp(-data.MeanReversion * t1) - Math.Exp(-data.MeanReversion * t2); double numerator = 2 * data.MeanReversion * data.MeanReversion * data.MeanReversion; int indexT0 = 1; // Period in which the time t0 is; volatilityTime[i-1] <= t0 < volatilityTime[i]; while (t0 > data.VolatilityTime.get(indexT0)) { indexT0++; } double[] s = new double[indexT0 + 1]; Array.Copy(data.VolatilityTime.toArray(), 0, s, 0, indexT0); s[indexT0] = t0; double factor2 = 0.0; double[] factorExp = new double[indexT0]; for (int loopperiod = 0; loopperiod < indexT0; loopperiod++) { factorExp[loopperiod] = (Math.Exp(data.MeanReversion * s[loopperiod + 1]) - Math.Exp(data.MeanReversion * s[loopperiod])) * (2 - Math.Exp(-data.MeanReversion * (t2 - s[loopperiod + 1])) - Math.Exp(-data.MeanReversion * (t2 - s[loopperiod]))); factor2 += data.Volatility.get(loopperiod) * data.Volatility.get(loopperiod) * factorExp[loopperiod]; } double factor = Math.Exp(factor1 / numerator * factor2); // Backward sweep double factorBar = 1.0; double factor2Bar = factor1 / numerator * factor * factorBar; double[] derivatives = new double[data.Volatility.size()]; for (int loopperiod = 0; loopperiod < indexT0; loopperiod++) { derivatives[loopperiod] = 2 * data.Volatility.get(loopperiod) * factorExp[loopperiod] * factor2Bar; } return(ValueDerivatives.of(factor, DoubleArray.ofUnsafe(derivatives))); }
/// <summary> /// Calculates the first order derivative of the swap rate with respect to the {@code alphaFixed} /// in the {@code P(*,theta)} numeraire. /// </summary> /// <param name="x"> the random variable value. </param> /// <param name="discountedCashFlowFixed"> the discounted cash flows equivalent of the swap fixed leg </param> /// <param name="alphaFixed"> the zero-coupon bond volatilities for the swap fixed leg </param> /// <param name="discountedCashFlowIbor"> the discounted cash flows equivalent of the swap Ibor leg </param> /// <param name="alphaIbor"> the zero-coupon bond volatilities for the swap Ibor leg </param> /// <returns> the swap rate and derivatives </returns> public ValueDerivatives swapRateDaf1(double x, DoubleArray discountedCashFlowFixed, DoubleArray alphaFixed, DoubleArray discountedCashFlowIbor, DoubleArray alphaIbor) { int sizeIbor = discountedCashFlowIbor.size(); int sizeFixed = discountedCashFlowFixed.size(); ArgChecker.isTrue(sizeIbor == alphaIbor.size(), "Length should be equal"); ArgChecker.isTrue(sizeFixed == alphaFixed.size(), "Length should be equal"); double[] expD = new double[sizeIbor]; double numerator = 0.0; for (int loopcf = 0; loopcf < sizeIbor; loopcf++) { numerator += discountedCashFlowIbor.get(loopcf) * Math.Exp(-alphaIbor.get(loopcf) * x - 0.5 * alphaIbor.get(loopcf) * alphaIbor.get(loopcf)); } double denominator = 0.0; for (int loopcf = 0; loopcf < sizeFixed; loopcf++) { expD[loopcf] = discountedCashFlowFixed.get(loopcf) * Math.Exp(-alphaFixed.get(loopcf) * x - 0.5 * alphaFixed.get(loopcf) * alphaFixed.get(loopcf)); denominator += expD[loopcf]; } double ratio = numerator / (denominator * denominator); double[] swapRateDaf1 = new double[sizeFixed]; for (int loopcf = 0; loopcf < sizeFixed; loopcf++) { swapRateDaf1[loopcf] = ratio * expD[loopcf] * (-x - alphaFixed.get(loopcf)); } return(ValueDerivatives.of(-numerator / denominator, DoubleArray.ofUnsafe(swapRateDaf1))); }
/// <summary> /// Test the adjoint algorithmic differentiation version of alpha. /// </summary> public virtual void alphaDSigma() { double expiry1 = 0.25; double expiry2 = 2.25; double numeraire = 10.0; double maturity = 9.0; int nbVolatility = VOLATILITY.size(); ValueDerivatives alphaDeriv = MODEL.alphaAdjoint(MODEL_PARAMETERS, expiry1, expiry2, numeraire, maturity); double alpha = alphaDeriv.Value; double[] alphaDerivatives = alphaDeriv.Derivatives.toArray(); double alpha2 = MODEL.alpha(MODEL_PARAMETERS, expiry1, expiry2, numeraire, maturity); assertEquals(alpha2, alpha, 1.0E-10); double shiftVol = 1.0E-6; double[] volatilityBumped = new double[nbVolatility]; Array.Copy(VOLATILITY.toArray(), 0, volatilityBumped, 0, nbVolatility); double[] alphaBumpedPlus = new double[nbVolatility]; double[] alphaBumpedMinus = new double[nbVolatility]; HullWhiteOneFactorPiecewiseConstantParameters parametersBumped; for (int loopvol = 0; loopvol < nbVolatility; loopvol++) { volatilityBumped[loopvol] += shiftVol; parametersBumped = HullWhiteOneFactorPiecewiseConstantParameters.of(MEAN_REVERSION, DoubleArray.copyOf(volatilityBumped), VOLATILITY_TIME); alphaBumpedPlus[loopvol] = MODEL.alpha(parametersBumped, expiry1, expiry2, numeraire, maturity); volatilityBumped[loopvol] -= 2 * shiftVol; parametersBumped = HullWhiteOneFactorPiecewiseConstantParameters.of(MEAN_REVERSION, DoubleArray.copyOf(volatilityBumped), VOLATILITY_TIME); alphaBumpedMinus[loopvol] = MODEL.alpha(parametersBumped, expiry1, expiry2, numeraire, maturity); assertEquals((alphaBumpedPlus[loopvol] - alphaBumpedMinus[loopvol]) / (2 * shiftVol), alphaDerivatives[loopvol], 1.0E-9); volatilityBumped[loopvol] = VOLATILITY.get(loopvol); } }
//------------------------------------------------------------------------- private ValueDerivatives priceDerivatives(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData data) { validate(option, ratesProvider, volatilities); validateData(option, ratesProvider, volatilities, data); int nSteps = data.NumberOfSteps; ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption; double timeToExpiry = data.getTime(nSteps); ResolvedFxSingle underlyingFx = underlyingOption.Underlying; Currency ccyBase = underlyingFx.CounterCurrencyPayment.Currency; Currency ccyCounter = underlyingFx.CounterCurrencyPayment.Currency; DiscountFactors baseDiscountFactors = ratesProvider.discountFactors(ccyBase); DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter); double rebateAtExpiry = 0d; // used to price knock-in option double rebateAtExpiryDerivative = 0d; // used to price knock-in option double notional = Math.Abs(underlyingFx.BaseCurrencyPayment.Amount); double[] rebateArray = new double[nSteps + 1]; SimpleConstantContinuousBarrier barrier = (SimpleConstantContinuousBarrier)option.Barrier; if (option.Rebate.Present) { CurrencyAmount rebateCurrencyAmount = option.Rebate.get(); double rebatePerUnit = rebateCurrencyAmount.Amount / notional; bool isCounter = rebateCurrencyAmount.Currency.Equals(ccyCounter); double rebate = isCounter ? rebatePerUnit : rebatePerUnit * barrier.BarrierLevel; if (barrier.KnockType.KnockIn) { // use in-out parity double dfCounterAtExpiry = counterDiscountFactors.discountFactor(timeToExpiry); double dfBaseAtExpiry = baseDiscountFactors.discountFactor(timeToExpiry); for (int i = 0; i < nSteps + 1; ++i) { rebateArray[i] = isCounter ? rebate * dfCounterAtExpiry / counterDiscountFactors.discountFactor(data.getTime(i)) : rebate * dfBaseAtExpiry / baseDiscountFactors.discountFactor(data.getTime(i)); } if (isCounter) { rebateAtExpiry = rebatePerUnit * dfCounterAtExpiry; } else { rebateAtExpiry = rebatePerUnit * data.Spot * dfBaseAtExpiry; rebateAtExpiryDerivative = rebatePerUnit * dfBaseAtExpiry; } } else { Arrays.fill(rebateArray, rebate); } } ConstantContinuousSingleBarrierKnockoutFunction barrierFunction = ConstantContinuousSingleBarrierKnockoutFunction.of(underlyingOption.Strike, timeToExpiry, underlyingOption.PutCall, nSteps, barrier.BarrierType, barrier.BarrierLevel, DoubleArray.ofUnsafe(rebateArray)); ValueDerivatives barrierPrice = TREE.optionPriceAdjoint(barrierFunction, data); if (barrier.KnockType.KnockIn) { // use in-out parity EuropeanVanillaOptionFunction vanillaFunction = EuropeanVanillaOptionFunction.of(underlyingOption.Strike, timeToExpiry, underlyingOption.PutCall, nSteps); ValueDerivatives vanillaPrice = TREE.optionPriceAdjoint(vanillaFunction, data); return(ValueDerivatives.of(vanillaPrice.Value + rebateAtExpiry - barrierPrice.Value, DoubleArray.of(vanillaPrice.getDerivative(0) + rebateAtExpiryDerivative - barrierPrice.getDerivative(0)))); } return(barrierPrice); }
private double[] toArray(ValueDerivatives valueDerivatives) { double[] derivatives = valueDerivatives.Derivatives.toArray(); double[] res = new double[derivatives.Length + 1]; res[0] = valueDerivatives.Value; Array.Copy(derivatives, 0, res, 1, derivatives.Length); return(res); }
/// <summary> /// Calculates the delta of the FX barrier option product. /// <para> /// The delta is the first derivative of <seealso cref="#price"/> with respect to spot. /// /// </para> /// </summary> /// <param name="option"> the option product </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="volatilities"> the Black volatility provider </param> /// <returns> the delta of the product </returns> public virtual double delta(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities) { if (volatilities.relativeTime(option.UnderlyingOption.Expiry) < 0d) { return(0d); } ValueDerivatives priceDerivatives = this.priceDerivatives(option, ratesProvider, volatilities); return(priceDerivatives.getDerivative(0)); }
/// <summary> /// Computes the derivative of the conventional cash annuity with respect to the yield from a swap leg. /// <para> /// The computation is relevant only for standard swaps with constant notional and regular payments. /// The swap leg must be a fixed leg. However, this is not checked internally. /// /// </para> /// </summary> /// <param name="fixedLeg"> the fixed leg of the swap </param> /// <param name="yield"> the yield </param> /// <returns> the cash annuity </returns> public virtual ValueDerivatives annuityCashDerivative(ResolvedSwapLeg fixedLeg, double yield) { int nbFixedPeriod = fixedLeg.PaymentPeriods.size(); SwapPaymentPeriod paymentPeriod = fixedLeg.PaymentPeriods.get(0); ArgChecker.isTrue(paymentPeriod is RatePaymentPeriod, "payment period should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod)paymentPeriod; int nbFixedPaymentYear = (int)(long)Math.Round(1d / ratePaymentPeriod.DayCount.yearFraction(ratePaymentPeriod.StartDate, ratePaymentPeriod.EndDate), MidpointRounding.AwayFromZero); double notional = Math.Abs(ratePaymentPeriod.Notional); ValueDerivatives annuityUnit = annuityCash1(nbFixedPaymentYear, nbFixedPeriod, yield); return(ValueDerivatives.of(annuityUnit.Value * notional, annuityUnit.Derivatives.multipliedBy(notional))); }
/// <summary> /// Calculates volatility and the adjoint (volatility sensitivity to forward, strike and model parameters). /// <para> /// By default the derivatives are computed by central finite difference approximation. /// This should be overridden in each subclass. /// /// </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 model data </param> /// <returns> the volatility and associated derivatives </returns> public virtual ValueDerivatives volatilityAdjoint(double forward, double strike, double timeToExpiry, T data) { ArgChecker.isTrue(forward >= 0.0, "forward must be greater than zero"); double[] res = new double[2 + data.NumberOfParameters]; // fwd, strike, the model parameters double volatility = this.volatility(forward, strike, timeToExpiry, data); res[0] = forwardBar(forward, strike, timeToExpiry, data); res[1] = strikeBar(forward, strike, timeToExpiry, data); System.Func <T, double> func = getVolatilityFunction(forward, strike, timeToExpiry); double[] modelAdjoint = paramBar(func, data); Array.Copy(modelAdjoint, 0, res, 2, data.NumberOfParameters); return(ValueDerivatives.of(volatility, DoubleArray.ofUnsafe(res))); }
/// <summary> /// Calculates the currency exposure of the FX barrier option product. /// <para> /// This assumes the tree is already calibrated and the tree data is stored as {@code RecombiningTrinomialTreeData}. /// The tree data should be consistent with the pricer and other inputs, see <seealso cref="#validateData"/>. /// /// </para> /// </summary> /// <param name="option"> the option product </param> /// <param name="ratesProvider"> the rates provider </param> /// <param name="volatilities"> the Black volatility provider </param> /// <param name="treeData"> the trinomial tree data </param> /// <returns> the currency exposure </returns> public virtual MultiCurrencyAmount currencyExposure(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData treeData) { ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption; ValueDerivatives priceDerivatives = this.priceDerivatives(option, ratesProvider, volatilities, treeData); double price = priceDerivatives.Value; double delta = priceDerivatives.getDerivative(0); CurrencyPair currencyPair = underlyingOption.Underlying.CurrencyPair; double todayFx = ratesProvider.fxRate(currencyPair); double signedNotional = this.signedNotional(underlyingOption); CurrencyAmount domestic = CurrencyAmount.of(currencyPair.Counter, (price - delta * todayFx) * signedNotional); CurrencyAmount foreign = CurrencyAmount.of(currencyPair.Base, delta * signedNotional); return(MultiCurrencyAmount.of(domestic, foreign)); }
/// <summary> /// Test consistency between price methods, and Greek via finite difference. /// </summary> public virtual void test_trinomialTree() { int nSteps = 135; double dt = TIME / nSteps; LatticeSpecification lattice = new CoxRossRubinsteinLatticeSpecification(); double fdEps = 1.0e-4; foreach (bool isCall in new bool[] { true, false }) { foreach (double strike in STRIKES) { foreach (double interest in INTERESTS) { foreach (double vol in VOLS) { foreach (double dividend in DIVIDENDS) { OptionFunction function = EuropeanVanillaOptionFunction.of(strike, TIME, PutCall.ofPut(!isCall), nSteps); double[] @params = lattice.getParametersTrinomial(vol, interest - dividend, dt).toArray(); DoubleArray time = DoubleArray.of(nSteps + 1, i => dt * i); DoubleArray df = DoubleArray.of(nSteps, i => Math.Exp(-interest * dt)); double[][] stateValue = new double[nSteps + 1][]; stateValue[0] = new double[] { SPOT }; IList <DoubleMatrix> prob = new List <DoubleMatrix>(); double[] probs = new double[] { @params[5], @params[4], @params[3] }; for (int i = 0; i < nSteps; ++i) { int index = i; stateValue[i + 1] = DoubleArray.of(2 * i + 3, j => SPOT * Math.Pow(@params[2], index + 1 - j) * Math.Pow(@params[1], j)).toArray(); double[][] probMatrix = new double[2 * i + 1][]; Arrays.fill(probMatrix, probs); prob.Add(DoubleMatrix.ofUnsafe(probMatrix)); } RecombiningTrinomialTreeData treeData = RecombiningTrinomialTreeData.of(DoubleMatrix.ofUnsafe(stateValue), prob, df, time); double priceData = TRINOMIAL_TREE.optionPrice(function, treeData); double priceParams = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT, vol, interest, dividend); assertEquals(priceData, priceParams); ValueDerivatives priceDeriv = TRINOMIAL_TREE.optionPriceAdjoint(function, treeData); assertEquals(priceDeriv.Value, priceData); double priceUp = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT + fdEps, vol, interest, dividend); double priceDw = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT - fdEps, vol, interest, dividend); double fdDelta = 0.5 * (priceUp - priceDw) / fdEps; assertEquals(priceDeriv.getDerivative(0), fdDelta, 3.0e-2); } } } } } }
//------------------------------------------------------------------------- /// <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> /// Computes the price and first order 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 volatility /// <li>[2] derivative with respect to the strike /// </ul> /// /// </para> /// </summary> /// <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="normalVol"> the normal volatility </param> /// <param name="numeraire"> the numeraire </param> /// <param name="putCall"> whether it is put or call </param> /// <returns> the price and associated derivatives </returns> public static ValueDerivatives priceAdjoint(double forward, double strike, double timeToExpiry, double normalVol, double numeraire, PutCall putCall) { int sign = putCall.Call ? 1 : -1; double price; double cdf = 0d; double pdf = 0d; double arg = 0d; double x = 0d; // Implementation Note: Forward sweep. double sigmaRootT = normalVol * Math.Sqrt(timeToExpiry); if (sigmaRootT < NormalFormulaRepository.NEAR_ZERO) { x = sign * (forward - strike); price = (x > 0 ? numeraire * x : 0d); } else { arg = sign * (forward - strike) / sigmaRootT; cdf = NormalFormulaRepository.DISTRIBUTION.getCDF(arg); pdf = NormalFormulaRepository.DISTRIBUTION.getPDF(arg); price = numeraire * (sign * (forward - strike) * cdf + sigmaRootT * pdf); } // Implementation Note: Backward sweep. double forwardDerivative; double volatilityDerivative; double strikeDerivative; double priceBar = 1d; if (sigmaRootT < NormalFormulaRepository.NEAR_ZERO) { double xBar = (x > 0 ? numeraire : 0d); forwardDerivative = sign * xBar; strikeDerivative = -forwardDerivative; volatilityDerivative = 0d; } else { double cdfBar = numeraire * (sign * (forward - strike)) * priceBar; double pdfBar = numeraire * sigmaRootT * priceBar; double argBar = pdf * cdfBar - pdf * arg * pdfBar; forwardDerivative = numeraire * sign * cdf * priceBar + sign / sigmaRootT * argBar; strikeDerivative = -forwardDerivative; double sigmaRootTBar = -arg / sigmaRootT * argBar + numeraire * pdf * priceBar; volatilityDerivative = Math.Sqrt(timeToExpiry) * sigmaRootTBar; } return(ValueDerivatives.of(price, DoubleArray.of(forwardDerivative, volatilityDerivative, strikeDerivative))); }
//------------------------------------------------------------------------- // The derivatives are [0] spot, [1] strike, [2] rate, [3] cost-of-carry, [4] volatility, [5] timeToExpiry, [6] spot twice private ValueDerivatives priceDerivatives(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities) { validate(option, ratesProvider, volatilities); SimpleConstantContinuousBarrier barrier = (SimpleConstantContinuousBarrier)option.Barrier; ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption; double[] derivatives = new double[7]; if (volatilities.relativeTime(underlyingOption.Expiry) < 0d) { return(ValueDerivatives.of(0d, DoubleArray.ofUnsafe(derivatives))); } ResolvedFxSingle underlyingFx = underlyingOption.Underlying; CurrencyPair currencyPair = underlyingFx.CurrencyPair; Currency ccyBase = currencyPair.Base; Currency ccyCounter = currencyPair.Counter; DiscountFactors baseDiscountFactors = ratesProvider.discountFactors(ccyBase); DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter); double rateBase = baseDiscountFactors.zeroRate(underlyingFx.PaymentDate); double rateCounter = counterDiscountFactors.zeroRate(underlyingFx.PaymentDate); double costOfCarry = rateCounter - rateBase; double dfBase = baseDiscountFactors.discountFactor(underlyingFx.PaymentDate); double dfCounter = counterDiscountFactors.discountFactor(underlyingFx.PaymentDate); double todayFx = ratesProvider.fxRate(currencyPair); double strike = underlyingOption.Strike; double forward = todayFx * dfBase / dfCounter; double volatility = volatilities.volatility(currencyPair, underlyingOption.Expiry, strike, forward); double timeToExpiry = volatilities.relativeTime(underlyingOption.Expiry); ValueDerivatives valueDerivatives = BARRIER_PRICER.priceAdjoint(todayFx, strike, timeToExpiry, costOfCarry, rateCounter, volatility, underlyingOption.PutCall.Call, barrier); if (!option.Rebate.Present) { return(valueDerivatives); } CurrencyAmount rebate = option.Rebate.get(); ValueDerivatives valueDerivativesRebate = rebate.Currency.Equals(ccyCounter) ? CASH_REBATE_PRICER.priceAdjoint(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType()) : ASSET_REBATE_PRICER.priceAdjoint(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType()); double rebateRate = rebate.Amount / Math.Abs(underlyingFx.BaseCurrencyPayment.Amount); double price = valueDerivatives.Value + rebateRate * valueDerivativesRebate.Value; derivatives[0] = valueDerivatives.getDerivative(0) + rebateRate * valueDerivativesRebate.getDerivative(0); derivatives[1] = valueDerivatives.getDerivative(1); for (int i = 2; i < 7; ++i) { derivatives[i] = valueDerivatives.getDerivative(i) + rebateRate * valueDerivativesRebate.getDerivative(i - 1); } return(ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives))); }
/// <summary> /// Calculate the true SABR delta and gamma and compare with that found by finite difference /// </summary> //JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes: //ORIGINAL LINE: @Test(enabled = false) public void testGreeks() public virtual void testGreeks() { double eps = 1e-3; double f = 1.2; double k = 1.4; double t = 5.0; double alpha = 0.3; double beta = 0.6; double rho = -0.4; double nu = 0.4; SabrFormulaData sabrData = SabrFormulaData.of(alpha, beta, rho, nu); ValueDerivatives adj = FUNCTION.volatilityAdjoint(f, k, t, sabrData); double bsDelta = BlackFormulaRepository.delta(f, k, t, adj.Value, true); double bsVega = BlackFormulaRepository.vega(f, k, t, adj.Value); double volForwardSense = adj.getDerivative(1); double delta = bsDelta + bsVega * volForwardSense; SabrFormulaData data = SabrFormulaData.of(alpha, beta, rho, nu); double volUp = FUNCTION.volatility(f + eps, k, t, data); double volDown = FUNCTION.volatility(f - eps, k, t, data); double priceUp = BlackFormulaRepository.price(f + eps, k, t, volUp, true); double price = BlackFormulaRepository.price(f, k, t, adj.Value, true); double priceDown = BlackFormulaRepository.price(f - eps, k, t, volDown, true); double fdDelta = (priceUp - priceDown) / 2 / eps; assertEquals(fdDelta, delta, 1e-6); double bsVanna = BlackFormulaRepository.vanna(f, k, t, adj.Value); double bsGamma = BlackFormulaRepository.gamma(f, k, t, adj.Value); double[] volD1 = new double[5]; //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[][] volD2 = new double[2][2]; double[][] volD2 = RectangularArrays.ReturnRectangularDoubleArray(2, 2); FUNCTION.volatilityAdjoint2(f, k, t, sabrData, volD1, volD2); double d2Sigmad2Fwd = volD2[0][0]; double gamma = bsGamma + 2 * bsVanna * adj.getDerivative(1) + bsVega * d2Sigmad2Fwd; double fdGamma = (priceUp + priceDown - 2 * price) / eps / eps; double d2Sigmad2FwdFD = (volUp + volDown - 2 * adj.Value) / eps / eps; assertEquals(d2Sigmad2FwdFD, d2Sigmad2Fwd, 1e-4); assertEquals(fdGamma, gamma, 1e-2); }
/// <summary> /// Computes the option price derivative with respect to the strike. /// <para> /// The price is SABR below the cut-off strike and extrapolated beyond. /// /// </para> /// </summary> /// <param name="strike"> the strike of the option </param> /// <param name="putCall"> whether the option is put or call </param> /// <returns> the option price derivative </returns> public double priceDerivativeStrike(double strike, PutCall putCall) { // Uses Hagan et al SABR function. if (strike <= cutOffStrike) { ValueDerivatives volatilityAdjoint = sabrFunction.volatilityAdjoint(forward, strike, timeToExpiry, sabrData); ValueDerivatives bsAdjoint = BlackFormulaRepository.priceAdjoint(forward, strike, timeToExpiry, volatilityAdjoint.Value, putCall.Equals(PutCall.CALL)); return(bsAdjoint.getDerivative(1) + bsAdjoint.getDerivative(3) * volatilityAdjoint.getDerivative(1)); } // Uses extrapolation for call. double pDK = extrapolationDerivative(strike); if (putCall.Put) { // Put by call/put parity pDK += 1.0; } return(pDK); }