/// <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))); }
//------------------------------------------------------------------------- private double[] computesFittingParameters() { double[] param = new double[3]; // Implementation note: called a,b,c in the note. // Computes derivatives at cut-off. double[] vD = 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[][] vD2 = new double[2][2]; double[][] vD2 = RectangularArrays.ReturnRectangularDoubleArray(2, 2); volatilityK = sabrFunction.volatilityAdjoint2(forward, cutOffStrike, timeToExpiry, sabrData, vD, vD2); Pair <ValueDerivatives, double[][]> pa2 = BlackFormulaRepository.priceAdjoint2(forward, cutOffStrike, timeToExpiry, volatilityK, true); double[] bsD = pa2.First.Derivatives.toArrayUnsafe(); double[][] bsD2 = pa2.Second; priceK[0] = pa2.First.Value; priceK[1] = bsD[1] + bsD[3] * vD[1]; priceK[2] = bsD2[1][1] + bsD2[1][2] * vD[1] + (bsD2[2][1] + bsD2[2][2] * vD[1]) * vD[1] + bsD[3] * vD2[1][1]; if (Math.Abs(priceK[0]) < SMALL_PRICE && Math.Abs(priceK[1]) < SMALL_PRICE && Math.Abs(priceK[2]) < SMALL_PRICE) { // Implementation note: If value and its derivatives is too small, then parameters are such that the extrapolated price is "very small". return(new double[] { -100.0, 0, 0 }); } System.Func <double, double> toSolveC = getCFunction(priceK, cutOffStrike, mu); BracketRoot bracketer = new BracketRoot(); double accuracy = 1.0E-5; RidderSingleRootFinder rootFinder = new RidderSingleRootFinder(accuracy); double[] range = bracketer.getBracketedPoints(toSolveC, -1.0, 1.0); param[2] = rootFinder.getRoot(toSolveC, range[0], range[1]).Value; param[1] = -2 * param[2] / cutOffStrike - (priceK[1] / priceK[0] * cutOffStrike + mu) * cutOffStrike; param[0] = Math.Log(priceK[0] / Math.Pow(cutOffStrike, -mu)) - param[1] / cutOffStrike - param[2] / (cutOffStrike * cutOffStrike); return(param); }
//JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes: //ORIGINAL LINE: @Test(enabled = true) public void log_normal_atm() public virtual void log_normal_atm() { double beta = 0.50; Surface betaSurface = ConstantSurface.of("Beta", beta).withMetadata(DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).zValueType(ValueType.SABR_BETA).surfaceName("Beta").build()); double shift = 0.0000; Surface shiftSurface = ConstantSurface.of("Shift", shift).withMetadata(DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).surfaceName("Shift").build()); SabrParametersSwaptionVolatilities calibratedSmile = SABR_CALIBRATION.calibrateWithFixedBetaAndShift(DEFINITION, CALIBRATION_TIME, DATA_SPARSE, MULTICURVE, betaSurface, shiftSurface); SabrParametersSwaptionVolatilities calibratedAtm = SABR_CALIBRATION.calibrateAlphaWithAtm(NAME_SABR, calibratedSmile, MULTICURVE, ATM_LOGNORMAL_SIMPLE, TENORS_SIMPLE, EXPIRIES_SIMPLE_2, INTERPOLATOR_2D); int nbExp = EXPIRIES_SIMPLE_2.size(); int nbTenor = TENORS_SIMPLE.size(); for (int loopexpiry = 0; loopexpiry < nbExp; loopexpiry++) { for (int looptenor = 0; looptenor < nbTenor; looptenor++) { double tenor = TENORS_SIMPLE.get(looptenor).get(ChronoUnit.YEARS); LocalDate expiry = EUR_FIXED_1Y_EURIBOR_6M.FloatingLeg.StartDateBusinessDayAdjustment.adjust(CALIBRATION_DATE.plus(EXPIRIES_SIMPLE_2.get(loopexpiry)), REF_DATA); LocalDate effectiveDate = EUR_FIXED_1Y_EURIBOR_6M.calculateSpotDateFromTradeDate(expiry, REF_DATA); LocalDate endDate = effectiveDate.plus(TENORS_SIMPLE.get(looptenor)); SwapTrade swap = EUR_FIXED_1Y_EURIBOR_6M.toTrade(CALIBRATION_DATE, effectiveDate, endDate, BuySell.BUY, 1.0, 0.0); double parRate = SWAP_PRICER.parRate(swap.resolve(REF_DATA).Product, MULTICURVE); ZonedDateTime expiryDateTime = expiry.atTime(11, 0).atZone(ZoneId.of("Europe/Berlin")); double time = calibratedAtm.relativeTime(expiryDateTime); double volBlack = calibratedAtm.volatility(expiryDateTime, tenor, parRate, parRate); double priceComputed = calibratedAtm.price(time, tenor, PutCall.CALL, parRate, parRate, volBlack); double priceBlack = BlackFormulaRepository.price(parRate, parRate, time, DATA_LOGNORMAL_ATM_SIMPLE[looptenor + loopexpiry * nbTenor], true); assertEquals(priceComputed, priceBlack, TOLERANCE_PRICE_CALIBRATION_ROOT); } } }
/// <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); }
public virtual void test_price_formula() { double sampleVol = 0.2; for (int i = 0; i < NB_TEST; i++) { double expiryTime = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]); for (int j = 0; j < NB_TEST; j++) { foreach (PutCall putCall in new PutCall[] { PutCall.CALL, PutCall.PUT }) { double price = VOLS.price(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol); double delta = VOLS.priceDelta(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol); double gamma = VOLS.priceGamma(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol); double theta = VOLS.priceTheta(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol); double vega = VOLS.priceVega(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol); assertEquals(price, BlackFormulaRepository.price(TEST_FORWARD, TEST_STRIKE[j], expiryTime, sampleVol, putCall.Call)); assertEquals(delta, BlackFormulaRepository.delta(TEST_FORWARD, TEST_STRIKE[j], expiryTime, sampleVol, putCall.Call)); assertEquals(gamma, BlackFormulaRepository.gamma(TEST_FORWARD, TEST_STRIKE[j], expiryTime, sampleVol)); assertEquals(theta, BlackFormulaRepository.driftlessTheta(TEST_FORWARD, TEST_STRIKE[j], expiryTime, sampleVol)); assertEquals(vega, BlackFormulaRepository.vega(TEST_FORWARD, TEST_STRIKE[j], expiryTime, sampleVol)); } } } }
//------------------------------------------------------------------------- public virtual void test_presentValueSensitivityBlackVolatility() { SwaptionSensitivity sensiRec = PRICER.presentValueSensitivityModelParamsVolatility(SWAPTION_REC_LONG, RATE_PROVIDER, VOLS); SwaptionSensitivity sensiPay = PRICER.presentValueSensitivityModelParamsVolatility(SWAPTION_PAY_SHORT, RATE_PROVIDER, VOLS); double forward = SWAP_PRICER.parRate(RSWAP_REC, RATE_PROVIDER); double annuityCash = SWAP_PRICER.LegPricer.annuityCash(RFIXED_LEG_REC, forward); double expiry = VOLS.relativeTime(SWAPTION_REC_LONG.Expiry); double tenor = VOLS.tenor(SETTLE, END); double volatility = SURFACE.zValue(expiry, tenor); double settle = ACT_ACT_ISDA.relativeYearFraction(VAL_DATE, SETTLE); double df = Math.Exp(-DSC_CURVE.yValue(settle) * settle); double expectedRec = df * annuityCash * BlackFormulaRepository.vega(forward, RATE, expiry, volatility); double expectedPay = -df *annuityCash *BlackFormulaRepository.vega(forward, RATE, expiry, volatility); assertEquals(sensiRec.Currency, EUR); assertEquals(sensiRec.Sensitivity, expectedRec, NOTIONAL * TOL); assertEquals(sensiRec.VolatilitiesName, VOLS.Name); assertEquals(sensiRec.Expiry, expiry); assertEquals(sensiRec.Tenor, 5.0); assertEquals(sensiRec.Strike, RATE); assertEquals(sensiRec.Forward, forward, TOL); assertEquals(sensiPay.Currency, EUR); assertEquals(sensiPay.Sensitivity, expectedPay, NOTIONAL * TOL); assertEquals(sensiRec.VolatilitiesName, VOLS.Name); assertEquals(sensiPay.Expiry, expiry); assertEquals(sensiPay.Tenor, 5.0); assertEquals(sensiPay.Strike, RATE); assertEquals(sensiPay.Forward, forward, TOL); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the price of the foreign exchange vanilla option product. /// <para> /// The price of the product is the value on the valuation date for one unit of the base currency /// and is expressed in the counter currency. The price does not take into account the long/short flag. /// See <seealso cref="#presentValue"/> for scaling and currency. /// /// </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 price of the product </returns> public virtual double price(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionSmileVolatilities volatilities) { validate(ratesProvider, volatilities); double timeToExpiry = volatilities.relativeTime(option.Expiry); if (timeToExpiry <= 0d) { return(0d); } ResolvedFxSingle underlyingFx = option.Underlying; Currency ccyCounter = option.CounterCurrency; double df = ratesProvider.discountFactor(ccyCounter, underlyingFx.PaymentDate); FxRate forward = fxPricer.forwardFxRate(underlyingFx, ratesProvider); CurrencyPair currencyPair = underlyingFx.CurrencyPair; double forwardRate = forward.fxRate(currencyPair); double strikeRate = option.Strike; bool isCall = option.PutCall.Call; SmileDeltaParameters smileAtTime = volatilities.Smile.smileForExpiry(timeToExpiry); double[] strikes = smileAtTime.strike(forwardRate).toArray(); double[] vols = smileAtTime.Volatility.toArray(); double volAtm = vols[1]; double[] x = vannaVolgaWeights(forwardRate, strikeRate, timeToExpiry, volAtm, strikes); double priceFwd = BlackFormulaRepository.price(forwardRate, strikeRate, timeToExpiry, volAtm, isCall); for (int i = 0; i < 3; i += 2) { double priceFwdAtm = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, volAtm, isCall); double priceFwdSmile = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, vols[i], isCall); priceFwd += x[i] * (priceFwdSmile - priceFwdAtm); } return(df * priceFwd); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the present value sensitivity to the SABR model parameters of the swaption product. /// <para> /// The sensitivity of the present value to the SABR model parameters, alpha, beta, rho and nu. /// /// </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 SABR model parameters </returns> public virtual PointSensitivityBuilder presentValueSensitivityModelParamsSabr(ResolvedSwaption swaption, RatesProvider ratesProvider, SabrSwaptionVolatilities swaptionVolatilities) { validate(swaption, ratesProvider, swaptionVolatilities); double expiry = swaptionVolatilities.relativeTime(swaption.Expiry); ResolvedSwap underlying = swaption.Underlying; ResolvedSwapLeg fixedLeg = this.fixedLeg(underlying); double tenor = swaptionVolatilities.tenor(fixedLeg.StartDate, fixedLeg.EndDate); double shift = swaptionVolatilities.shift(expiry, tenor); double pvbp = SwapPricer.LegPricer.pvbp(fixedLeg, ratesProvider); double strike = SwapPricer.LegPricer.couponEquivalent(fixedLeg, ratesProvider, pvbp); if (expiry < 0d) { // Option has expired already return(PointSensitivityBuilder.none()); } double forward = SwapPricer.parRate(underlying, ratesProvider); double volatility = swaptionVolatilities.volatility(expiry, tenor, strike, forward); DoubleArray derivative = swaptionVolatilities.volatilityAdjoint(expiry, tenor, strike, forward).Derivatives; // Backward sweep double vega = Math.Abs(pvbp) * BlackFormulaRepository.vega(forward + shift, strike + shift, expiry, volatility) * swaption.LongShort.sign(); // sensitivities Currency ccy = fixedLeg.Currency; SwaptionVolatilitiesName name = swaptionVolatilities.Name; return(PointSensitivityBuilder.of(SwaptionSabrSensitivity.of(name, expiry, tenor, ALPHA, ccy, vega * derivative.get(2)), SwaptionSabrSensitivity.of(name, expiry, tenor, BETA, ccy, vega * derivative.get(3)), SwaptionSabrSensitivity.of(name, expiry, tenor, RHO, ccy, vega * derivative.get(4)), SwaptionSabrSensitivity.of(name, expiry, tenor, NU, ccy, vega * derivative.get(5)))); }
private const double TOLERANCE_PRICE_CALIBRATION_LS = 5.0E-4; // Calibration Least Square; result not exact //JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes: //ORIGINAL LINE: @Test public void normal_cube() public virtual void normal_cube() { double beta = 0.50; Surface betaSurface = ConstantSurface.of("Beta", beta).withMetadata(DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).zValueType(ValueType.SABR_BETA).surfaceName("Beta").build()); double shift = 0.0300; Surface shiftSurface = ConstantSurface.of("Shift", shift).withMetadata(DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).surfaceName("Shift").build()); SabrParametersSwaptionVolatilities calibrated = SABR_CALIBRATION.calibrateWithFixedBetaAndShift(DEFINITION, CALIBRATION_TIME, DATA_SPARSE, MULTICURVE, betaSurface, shiftSurface); for (int looptenor = 0; looptenor < TENORS.size(); looptenor++) { double tenor = TENORS.get(looptenor).get(ChronoUnit.YEARS); for (int loopexpiry = 0; loopexpiry < EXPIRIES.size(); loopexpiry++) { LocalDate expiry = EUR_FIXED_1Y_EURIBOR_6M.FloatingLeg.StartDateBusinessDayAdjustment.adjust(CALIBRATION_DATE.plus(EXPIRIES.get(loopexpiry)), REF_DATA); LocalDate effectiveDate = EUR_FIXED_1Y_EURIBOR_6M.calculateSpotDateFromTradeDate(expiry, REF_DATA); LocalDate endDate = effectiveDate.plus(TENORS.get(looptenor)); SwapTrade swap = EUR_FIXED_1Y_EURIBOR_6M.toTrade(CALIBRATION_DATE, effectiveDate, endDate, BuySell.BUY, 1.0, 0.0); double parRate = SWAP_PRICER.parRate(swap.resolve(REF_DATA).Product, MULTICURVE); ZonedDateTime expiryDateTime = expiry.atTime(11, 0).atZone(ZoneId.of("Europe/Berlin")); double time = calibrated.relativeTime(expiryDateTime); for (int loopmoney = 0; loopmoney < MONEYNESS.size(); loopmoney++) { if (!double.IsNaN(DATA_ARRAY_SPARSE[looptenor][loopexpiry][loopmoney])) { double strike = parRate + MONEYNESS.get(loopmoney); double volBlack = calibrated.volatility(expiryDateTime, tenor, strike, parRate); double priceComputed = BlackFormulaRepository.price(parRate + shift, parRate + MONEYNESS.get(loopmoney) + shift, time, volBlack, true); double priceNormal = NormalFormulaRepository.price(parRate, parRate + MONEYNESS.get(loopmoney), time, DATA_ARRAY_SPARSE[looptenor][loopexpiry][loopmoney], PutCall.CALL); assertEquals(priceComputed, priceNormal, TOLERANCE_PRICE_CALIBRATION_LS); } } } } }
//------------------------------------------------------------------------- /// <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))); }
public virtual InterpolatedNodalSurface localVolatilityFromPrice(Surface callPriceSurface, double spot, System.Func <double, double> interestRate, System.Func <double, double> dividendRate) { double[][] stateValue = new double[nSteps + 1][]; double[] df = new double[nSteps]; IList <DoubleMatrix> probability = new List <DoubleMatrix>(nSteps); int nTotal = (nSteps - 1) * (nSteps - 1) + 1; double[] timeRes = new double[nTotal]; double[] spotRes = new double[nTotal]; double[] volRes = new double[nTotal]; // uniform grid based on TrigeorgisLatticeSpecification, using reference values double refPrice = callPriceSurface.zValue(maxTime, spot) * Math.Exp(interestRate(maxTime) * maxTime); double refForward = spot * Math.Exp((interestRate(maxTime) - dividendRate(maxTime)) * maxTime); double refVolatility = BlackFormulaRepository.impliedVolatility(refPrice, refForward, spot, maxTime, true); double dt = maxTime / nSteps; double dx = refVolatility * Math.Sqrt(3d * dt); double upFactor = Math.Exp(dx); double downFactor = Math.Exp(-dx); double[] adSec = new double[2 * nSteps + 1]; double[] assetPrice = new double[2 * nSteps + 1]; for (int i = nSteps; i > -1; --i) { if (i == 0) { resolveFirstLayer(interestRate, dividendRate, nTotal, dt, spot, adSec, assetPrice, timeRes, spotRes, volRes, df, stateValue, probability); } else { double time = dt * i; double zeroRate = interestRate(time); double zeroDividendRate = dividendRate(time); int nNodes = 2 * i + 1; double[] assetPriceLocal = new double[nNodes]; double[] callOptionPrice = new double[nNodes]; double[] putOptionPrice = new double[nNodes]; int position = i - 1; double assetTmp = spot * Math.Pow(upFactor, i); // call options for upper half nodes for (int j = nNodes - 1; j > position - 1; --j) { assetPriceLocal[j] = assetTmp; callOptionPrice[j] = callPriceSurface.zValue(time, assetPriceLocal[j]); assetTmp *= downFactor; } // put options for lower half nodes assetTmp = spot * Math.Pow(downFactor, i); for (int j = 0; j < position + 2; ++j) { assetPriceLocal[j] = assetTmp; putOptionPrice[j] = callPriceSurface.zValue(time, assetPriceLocal[j]) - spot * Math.Exp(-zeroDividendRate * time) + Math.Exp(-zeroRate * time) * assetPriceLocal[j]; assetTmp *= upFactor; } resolveLayer(interestRate, dividendRate, i, nTotal, position, dt, zeroRate, zeroDividendRate, callOptionPrice, putOptionPrice, adSec, assetPrice, assetPriceLocal, timeRes, spotRes, volRes, df, stateValue, probability); } } SurfaceMetadata metadata = DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.STRIKE).zValueType(ValueType.LOCAL_VOLATILITY).surfaceName(SurfaceName.of("localVol_" + callPriceSurface.Name)).build(); return(InterpolatedNodalSurface.ofUnsorted(metadata, DoubleArray.ofUnsafe(timeRes), DoubleArray.ofUnsafe(spotRes), DoubleArray.ofUnsafe(volRes), interpolator)); }
private void priceCheck(double[] strikes) { for (int i = 0; i < N; i++) { double ivNormalComputed = NormalFormulaRepository.impliedVolatilityFromBlackApproximated(FORWARD, strikes[i], T, SIGMA_BLACK[i]); double priceNormalComputed = NormalFormulaRepository.price(FORWARD, strikes[i], T, ivNormalComputed, PutCall.CALL) * DF; double priceBlack = BlackFormulaRepository.price(FORWARD, strikes[i], T, SIGMA_BLACK[i], true) * DF; assertEquals(priceNormalComputed, priceBlack, TOLERANCE_PRICE); } }
public virtual void test_theta_from_future_price() { double futurePrice = 1.1d; double computed = OPTION_PRICER.theta(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOLS, futurePrice); double strike = FUTURE_OPTION_PRODUCT.StrikePrice; double expiryTime = ACT_365F.relativeYearFraction(VAL_DATE, FUTURE_OPTION_PRODUCT.ExpiryDate); double logMoneyness = Math.Log(strike / futurePrice); double vol = SURFACE.zValue(expiryTime, logMoneyness); double expected = BlackFormulaRepository.driftlessTheta(futurePrice, strike, expiryTime, vol); assertEquals(computed, expected, TOL); }
public virtual void test_price() { double computed = OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOLS); double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.UnderlyingFuture, RATE_PROVIDER); double strike = FUTURE_OPTION_PRODUCT.StrikePrice; double expiryTime = ACT_365F.relativeYearFraction(VAL_DATE, FUTURE_OPTION_PRODUCT.ExpiryDate); double logMoneyness = Math.Log(strike / futurePrice); double vol = SURFACE.zValue(expiryTime, logMoneyness); double expected = BlackFormulaRepository.price(futurePrice, strike, expiryTime, vol, true); assertEquals(computed, expected, TOL); }
//------------------------------------------------------------------------- public virtual void present_value_theta_formula() { double forward = PRICER_SWAP.parRate(RSWAP_REC, MULTI_USD); double pvbp = PRICER_SWAP.LegPricer.pvbp(RSWAP_REC.getLegs(SwapLegType.FIXED).get(0), MULTI_USD); double volatility = BLACK_VOLS_USD_STD.volatility(SWAPTION_LONG_REC.Expiry, SWAP_TENOR_YEAR, STRIKE, forward); double expiry = BLACK_VOLS_USD_STD.relativeTime(SWAPTION_LONG_REC.Expiry); double pvThetaExpected = BlackFormulaRepository.driftlessTheta(forward, STRIKE, expiry, volatility) * Math.Abs(pvbp); CurrencyAmount pvThetaComputed = PRICER_SWAPTION_BLACK.presentValueTheta(SWAPTION_LONG_REC, MULTI_USD, BLACK_VOLS_USD_STD); assertEquals(pvThetaComputed.Currency, USD); assertEquals(pvThetaComputed.Amount, pvThetaExpected, TOLERANCE_PV); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the strikes in ascending order. /// <para> /// The result has twice the number of values plus one as the delta/volatility. /// The put with lower delta (in absolute value) first, at-the-money and call with larger delta first. /// /// </para> /// </summary> /// <param name="forward"> the forward </param> /// <returns> the strikes </returns> public DoubleArray strike(double forward) { int nbDelta = delta.size(); double[] strike = new double[2 * nbDelta + 1]; strike[nbDelta] = forward * Math.Exp(volatility.get(nbDelta) * volatility.get(nbDelta) * expiry / 2.0); for (int loopdelta = 0; loopdelta < nbDelta; loopdelta++) { strike[loopdelta] = BlackFormulaRepository.impliedStrike(-delta.get(loopdelta), false, forward, expiry, volatility.get(loopdelta)); // Put strike[2 * nbDelta - loopdelta] = BlackFormulaRepository.impliedStrike(delta.get(loopdelta), true, forward, expiry, volatility.get(2 * nbDelta - loopdelta)); // Call } return(DoubleArray.ofUnsafe(strike)); }
private void checkCalibrationNormal(DoubleArray moneyness, DoubleArray normalVol, DoubleArray startParameters, BitArray @fixed, double shift, double tolerance) { Pair <LeastSquareResultsWithTransform, DoubleArray> rComputed = SABR_CALIBRATION.calibrateLsShiftedFromNormalVolatilities(BDA, CALIBRATION_TIME, ACT_365F, EXPIRY_PERIOD, FORWARD, moneyness, ValueType.SIMPLE_MONEYNESS, normalVol, startParameters, @fixed, shift); SabrFormulaData sabrComputed = SabrFormulaData.of(rComputed.First.ModelParameters.toArrayUnsafe()); for (int i = 0; i < moneyness.size(); i++) { double ivComputed = SABR_FORMULA.volatility(FORWARD + shift, FORWARD + moneyness.get(i) + shift, TIME_EXPIRY, sabrComputed.Alpha, sabrComputed.Beta, sabrComputed.Rho, sabrComputed.Nu); double priceComputed = BlackFormulaRepository.price(FORWARD + shift, FORWARD + moneyness.get(i) + shift, TIME_EXPIRY, ivComputed, true); double priceNormal = NormalFormulaRepository.price(FORWARD, FORWARD + moneyness.get(i), TIME_EXPIRY, normalVol.get(i), PutCall.CALL); assertEquals(priceComputed, priceNormal, tolerance); } }
//------------------------------------------------------------------------- public virtual void test_presentValue() { CurrencyAmount computedRec = SWAPTION_PRICER.presentValue(SWAPTION_REC_LONG, RATE_PROVIDER, VOLS); CurrencyAmount computedPay = SWAPTION_PRICER.presentValue(SWAPTION_PAY_SHORT, RATE_PROVIDER, VOLS); double forward = SWAP_PRICER.parRate(RSWAP_REC, RATE_PROVIDER); double pvbp = SWAP_PRICER.LegPricer.pvbp(RSWAP_REC.getLegs(SwapLegType.FIXED).get(0), RATE_PROVIDER); double volatility = VOLS.volatility(SWAPTION_REC_LONG.Expiry, TENOR_YEAR, RATE, forward); double maturity = VOLS.relativeTime(SWAPTION_REC_LONG.Expiry); double expectedRec = pvbp * BlackFormulaRepository.price(forward + SwaptionSabrRateVolatilityDataSet.SHIFT, RATE + SwaptionSabrRateVolatilityDataSet.SHIFT, maturity, volatility, false); double expectedPay = -pvbp *BlackFormulaRepository.price(forward + SwaptionSabrRateVolatilityDataSet.SHIFT, RATE + SwaptionSabrRateVolatilityDataSet.SHIFT, maturity, volatility, true); assertEquals(computedRec.Currency, USD); assertEquals(computedRec.Amount, expectedRec, NOTIONAL * TOL); assertEquals(computedPay.Currency, USD); assertEquals(computedPay.Amount, expectedPay, NOTIONAL * TOL); }
/// <summary> /// Calculates the currency exposure of the foreign exchange vanilla option product. /// </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 currency exposure </returns> public virtual MultiCurrencyAmount currencyExposure(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionSmileVolatilities volatilities) { validate(ratesProvider, volatilities); double timeToExpiry = volatilities.relativeTime(option.Expiry); if (timeToExpiry <= 0d) { return(MultiCurrencyAmount.empty()); } ResolvedFxSingle underlyingFx = option.Underlying; Currency ccyCounter = option.CounterCurrency; double df = ratesProvider.discountFactor(ccyCounter, underlyingFx.PaymentDate); FxRate forward = fxPricer.forwardFxRate(underlyingFx, ratesProvider); CurrencyPair currencyPair = underlyingFx.CurrencyPair; double spot = ratesProvider.fxRate(currencyPair); double forwardRate = forward.fxRate(currencyPair); double fwdRateSpotSensitivity = fxPricer.forwardFxRateSpotSensitivity(option.PutCall.Call ? underlyingFx : underlyingFx.inverse(), ratesProvider); double strikeRate = option.Strike; bool isCall = option.PutCall.Call; SmileDeltaParameters smileAtTime = volatilities.Smile.smileForExpiry(timeToExpiry); double[] strikes = smileAtTime.strike(forwardRate).toArray(); double[] vols = smileAtTime.Volatility.toArray(); double volAtm = vols[1]; double[] x = vannaVolgaWeights(forwardRate, strikeRate, timeToExpiry, volAtm, strikes); double priceFwd = BlackFormulaRepository.price(forwardRate, strikeRate, timeToExpiry, volAtm, isCall); double deltaFwd = BlackFormulaRepository.delta(forwardRate, strikeRate, timeToExpiry, volAtm, isCall); for (int i = 0; i < 3; i += 2) { double priceFwdAtm = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, volAtm, isCall); double priceFwdSmile = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, vols[i], isCall); priceFwd += x[i] * (priceFwdSmile - priceFwdAtm); double deltaFwdAtm = BlackFormulaRepository.delta(forwardRate, strikes[i], timeToExpiry, volAtm, isCall); double deltaFwdSmile = BlackFormulaRepository.delta(forwardRate, strikes[i], timeToExpiry, vols[i], isCall); deltaFwd += x[i] * (deltaFwdSmile - deltaFwdAtm); } double price = df * priceFwd; double delta = df * deltaFwd * fwdRateSpotSensitivity; double signedNotional = this.signedNotional(option); CurrencyAmount domestic = CurrencyAmount.of(currencyPair.Counter, (price - delta * spot) * signedNotional); CurrencyAmount foreign = CurrencyAmount.of(currencyPair.Base, delta * signedNotional); return(MultiCurrencyAmount.of(domestic, foreign)); }
//------------------------------------------------------------------------- /// <summary> /// Computes the option price with numeraire=1. /// <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 </returns> public double price(double strike, PutCall putCall) { // Uses Hagan et al SABR function. if (strike <= cutOffStrike) { double volatility = sabrFunction.volatility(forward, strike, timeToExpiry, sabrData); return(BlackFormulaRepository.price(forward, strike, timeToExpiry, volatility, putCall.Call)); } // Uses extrapolation for call. double price = extrapolation(strike); if (putCall.Put) { // Put by call/put parity price -= (forward - strike); } return(price); }
/// <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); }
//------------------------------------------------------------------------- public virtual void test_presentValueTheta() { CurrencyAmount computedRec = PRICER.presentValueTheta(SWAPTION_REC_LONG, RATE_PROVIDER, VOLS); CurrencyAmount computedPay = PRICER.presentValueTheta(SWAPTION_PAY_SHORT, RATE_PROVIDER, VOLS); double forward = SWAP_PRICER.parRate(RSWAP_REC, RATE_PROVIDER); double annuityCash = SWAP_PRICER.LegPricer.annuityCash(RFIXED_LEG_REC, forward); double expiry = VOLS.relativeTime(SWAPTION_REC_LONG.Expiry); double tenor = VOLS.tenor(SETTLE, END); double volatility = SURFACE.zValue(expiry, tenor); double settle = ACT_ACT_ISDA.relativeYearFraction(VAL_DATE, SETTLE); double df = Math.Exp(-DSC_CURVE.yValue(settle) * settle); double expectedRec = df * annuityCash * BlackFormulaRepository.driftlessTheta(forward, RATE, expiry, volatility); double expectedPay = -df *annuityCash *BlackFormulaRepository.driftlessTheta(forward, RATE, expiry, volatility); assertEquals(computedRec.Currency, EUR); assertEquals(computedRec.Amount, expectedRec, NOTIONAL * TOL); assertEquals(computedPay.Currency, EUR); assertEquals(computedPay.Amount, expectedPay, NOTIONAL * TOL); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the Black theta of the foreign exchange vanilla option product. /// <para> /// The theta is the negative of the first derivative of <seealso cref="#price"/> with respect to time parameter /// in Black formula (the discounted driftless theta). /// /// </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 theta of the product </returns> public virtual double theta(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities) { double timeToExpiry = volatilities.relativeTime(option.Expiry); if (timeToExpiry <= 0d) { return(0d); } ResolvedFxSingle underlying = option.Underlying; FxRate forward = fxPricer.forwardFxRate(underlying, ratesProvider); CurrencyPair strikePair = underlying.CurrencyPair; double forwardRate = forward.fxRate(strikePair); double strikeRate = option.Strike; double volatility = volatilities.volatility(strikePair, option.Expiry, strikeRate, forwardRate); double fwdTheta = BlackFormulaRepository.driftlessTheta(forwardRate, strikeRate, timeToExpiry, volatility); double discountFactor = ratesProvider.discountFactor(option.CounterCurrency, underlying.PaymentDate); return(discountFactor * fwdTheta); }
//------------------------------------------------------------------------- /// <summary> /// Calculates the present value sensitivity of the foreign exchange vanilla option product. /// <para> /// The present value sensitivity of the product is the sensitivity of <seealso cref="#presentValue"/> to /// the underlying curves. /// </para> /// <para> /// The implied strikes and weights are fixed in this sensitivity computation. /// /// </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 present value curve sensitivity of the product </returns> public virtual PointSensitivityBuilder presentValueSensitivityRatesStickyStrike(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionSmileVolatilities volatilities) { validate(ratesProvider, volatilities); double timeToExpiry = volatilities.relativeTime(option.Expiry); if (timeToExpiry <= 0d) { return(PointSensitivityBuilder.none()); } ResolvedFxSingle underlyingFx = option.Underlying; Currency ccyCounter = option.CounterCurrency; double df = ratesProvider.discountFactor(ccyCounter, underlyingFx.PaymentDate); FxRate forward = fxPricer.forwardFxRate(underlyingFx, ratesProvider); CurrencyPair currencyPair = underlyingFx.CurrencyPair; double forwardRate = forward.fxRate(currencyPair); double strikeRate = option.Strike; bool isCall = option.PutCall.Call; SmileDeltaParameters smileAtTime = volatilities.Smile.smileForExpiry(timeToExpiry); double[] strikes = smileAtTime.strike(forwardRate).toArray(); double[] vols = smileAtTime.Volatility.toArray(); double volAtm = vols[1]; double[] x = vannaVolgaWeights(forwardRate, strikeRate, timeToExpiry, volAtm, strikes); double priceFwd = BlackFormulaRepository.price(forwardRate, strikeRate, timeToExpiry, volAtm, isCall); double deltaFwd = BlackFormulaRepository.delta(forwardRate, strikeRate, timeToExpiry, volAtm, isCall); for (int i = 0; i < 3; i += 2) { double priceFwdAtm = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, volAtm, isCall); double priceFwdSmile = BlackFormulaRepository.price(forwardRate, strikes[i], timeToExpiry, vols[i], isCall); priceFwd += x[i] * (priceFwdSmile - priceFwdAtm); double deltaFwdAtm = BlackFormulaRepository.delta(forwardRate, strikes[i], timeToExpiry, volAtm, isCall); double deltaFwdSmile = BlackFormulaRepository.delta(forwardRate, strikes[i], timeToExpiry, vols[i], isCall); deltaFwd += x[i] * (deltaFwdSmile - deltaFwdAtm); } double signedNotional = this.signedNotional(option); PointSensitivityBuilder dfSensi = ratesProvider.discountFactors(ccyCounter).zeroRatePointSensitivity(underlyingFx.PaymentDate).multipliedBy(priceFwd * signedNotional); PointSensitivityBuilder fwdSensi = fxPricer.forwardFxRatePointSensitivity(option.PutCall.Call ? underlyingFx : underlyingFx.inverse(), ratesProvider).multipliedBy(df * deltaFwd * signedNotional); return(dfSensi.combinedWith(fwdSensi)); }
private double[] vannaVolgaWeights(double forward, double strike, double timeToExpiry, double volATM, double[] strikesReference) { double lnk21 = Math.Log(strikesReference[1] / strikesReference[0]); double lnk31 = Math.Log(strikesReference[2] / strikesReference[0]); double lnk32 = Math.Log(strikesReference[2] / strikesReference[1]); double[] lnk = new double[3]; for (int loopvv = 0; loopvv < 3; loopvv++) { lnk[loopvv] = Math.Log(strikesReference[loopvv] / strike); } double[] x = new double[3]; double vega0 = BlackFormulaRepository.vega(forward, strikesReference[0], timeToExpiry, volATM); double vegaFlat = BlackFormulaRepository.vega(forward, strike, timeToExpiry, volATM); double vega2 = BlackFormulaRepository.vega(forward, strikesReference[2], timeToExpiry, volATM); x[0] = vegaFlat * lnk[1] * lnk[2] / (vega0 * lnk21 * lnk31); x[2] = vegaFlat * lnk[0] * lnk[1] / (vega2 * lnk31 * lnk32); return(x); }
/// <summary> /// Tests the price for options in SABR model with extrapolation. /// </summary> public virtual void price() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; double volatilityIn = SABR_FUNCTION.volatility(FORWARD, strikeIn, TIME_TO_EXPIRY, SABR_DATA); double priceExpectedIn = BlackFormulaRepository.price(FORWARD, strikeIn, TIME_TO_EXPIRY, volatilityIn, true); double priceIn = SABR_EXTRAPOLATION.price(strikeIn, PutCall.CALL); assertEquals(priceExpectedIn, priceIn, TOLERANCE_PRICE); double volatilityAt = SABR_FUNCTION.volatility(FORWARD, strikeAt, TIME_TO_EXPIRY, SABR_DATA); double priceExpectedAt = BlackFormulaRepository.price(FORWARD, strikeAt, TIME_TO_EXPIRY, volatilityAt, true); double priceAt = SABR_EXTRAPOLATION.price(strikeAt, PutCall.CALL); assertEquals(priceExpectedAt, priceAt, TOLERANCE_PRICE); double priceOut = SABR_EXTRAPOLATION.price(strikeOut, PutCall.CALL); double priceExpectedOut = 5.427104E-5; // From previous run assertEquals(priceExpectedOut, priceOut, TOLERANCE_PRICE); }
//------------------------------------------------------------------------- public virtual void test_presentValueSensitivity() { PointSensitivities point = OPTION_TRADE_PRICER.presentValueSensitivityRates(OPTION_TRADE, RATE_PROVIDER, VOLS); CurrencyParameterSensitivities computed = RATE_PROVIDER.parameterSensitivity(point); double futurePrice = FUTURE_PRICER.price(OPTION_PRODUCT.UnderlyingFuture, RATE_PROVIDER); double strike = OPTION_PRODUCT.StrikePrice; double expiryTime = ACT_365F.relativeYearFraction(VAL_DATE, OPTION_PRODUCT.ExpiryDate); double logMoneyness = Math.Log(strike / futurePrice); double logMoneynessUp = Math.Log(strike / (futurePrice + EPS)); double logMoneynessDw = Math.Log(strike / (futurePrice - EPS)); double vol = SURFACE.zValue(expiryTime, logMoneyness); double volUp = SURFACE.zValue(expiryTime, logMoneynessUp); double volDw = SURFACE.zValue(expiryTime, logMoneynessDw); double volSensi = 0.5 * (volUp - volDw) / EPS; double vega = BlackFormulaRepository.vega(futurePrice, strike, expiryTime, vol); CurrencyParameterSensitivities sensiVol = RATE_PROVIDER.parameterSensitivity(FUTURE_PRICER.priceSensitivity(OPTION_PRODUCT.UnderlyingFuture, RATE_PROVIDER)).multipliedBy(-vega * volSensi * NOTIONAL * QUANTITY); CurrencyParameterSensitivities expected = FD_CAL.sensitivity(RATE_PROVIDER, (p) => OPTION_TRADE_PRICER.presentValue(OPTION_TRADE, (p), VOLS, REFERENCE_PRICE)); assertTrue(computed.equalWithTolerance(expected.combinedWith(sensiVol), 30d * EPS * NOTIONAL * QUANTITY)); }
/// <summary> /// Upper barrier level is very high: knock-in is close to 0, knock-out is close to vanilla. /// </summary> public virtual void largeBarrierTest() { SimpleConstantContinuousBarrier upIn = SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_IN, 1.0e4); SimpleConstantContinuousBarrier upOut = SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_OUT, 1.0e4); foreach (double strike in STRIKES) { // call double callVanilla = BlackFormulaRepository.price(FWD_FX, strike, EXPIRY_TIME, VOLATILITY, true) * DF_DOM; double callUpIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, upIn); double callUpOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, upOut); assertRelative(callUpIn, 0d); assertRelative(callUpOut, callVanilla); // put double putVanilla = BlackFormulaRepository.price(FWD_FX, strike, EXPIRY_TIME, VOLATILITY, false) * DF_DOM; double putUpIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, upIn); double putUpOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, upOut); assertRelative(putUpIn, 0d); assertRelative(putUpOut, putVanilla); } }
/// <summary> /// Check "in + out = vanilla" is satisfied. /// </summary> public virtual void inOutParity() { foreach (double strike in STRIKES) { // call double callVanilla = BlackFormulaRepository.price(FWD_FX, strike, EXPIRY_TIME, VOLATILITY, true) * DF_DOM; double callUpIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, BARRIER_UP_IN); double callUpOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, BARRIER_UP_OUT); double callDownIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, BARRIER_DOWN_IN); double callDownOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, true, BARRIER_DOWN_OUT); assertRelative(callUpIn + callUpOut, callVanilla); assertRelative(callDownIn + callDownOut, callVanilla); // put double putVanilla = BlackFormulaRepository.price(FWD_FX, strike, EXPIRY_TIME, VOLATILITY, false) * DF_DOM; double putUpIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, BARRIER_UP_IN); double putUpOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, BARRIER_UP_OUT); double putDownIn = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, BARRIER_DOWN_IN); double putDownOut = BARRIER_PRICER.price(SPOT, strike, EXPIRY_TIME, COST_OF_CARRY, RATE_DOM, VOLATILITY, false, BARRIER_DOWN_OUT); assertRelative(putUpIn + putUpOut, putVanilla); assertRelative(putDownIn + putDownOut, putVanilla); } }