//------------------------------------------------------------------------- public virtual void test_price_presentValue() { for (int i = 0; i < NB_STRIKES; ++i) { ResolvedFxVanillaOption call = CALLS[i]; ResolvedFxVanillaOptionTrade callTrade = ResolvedFxVanillaOptionTrade.builder().product(call).premium(Payment.of(EUR, 0, VAL_DATE)).build(); double computedPriceCall = PRICER.price(call, RATES_PROVIDER, VOLS); CurrencyAmount computedCall = PRICER.presentValue(call, RATES_PROVIDER, VOLS); double timeToExpiry = VOLS.relativeTime(EXPIRY); FxRate forward = FX_PRICER.forwardFxRate(UNDERLYING[i], RATES_PROVIDER); double forwardRate = forward.fxRate(CURRENCY_PAIR); double strikeRate = call.Strike; SmileDeltaParameters smileAtTime = VOLS.Smile.smileForExpiry(timeToExpiry); double[] strikes = smileAtTime.strike(forwardRate).toArray(); double[] vols = smileAtTime.Volatility.toArray(); double df = RATES_PROVIDER.discountFactor(USD, PAY); double[] weights = this.weights(forwardRate, strikeRate, strikes, timeToExpiry, vols[1]); double expectedPriceCall = BlackFormulaRepository.price(forwardRate, strikeRate, timeToExpiry, vols[1], true); for (int j = 0; j < 3; ++j) { expectedPriceCall += weights[j] * (BlackFormulaRepository.price(forwardRate, strikes[j], timeToExpiry, vols[j], true) - BlackFormulaRepository.price(forwardRate, strikes[j], timeToExpiry, vols[1], true)); } expectedPriceCall *= df; assertEquals(computedPriceCall, expectedPriceCall, TOL); assertEquals(computedCall.Amount, expectedPriceCall * NOTIONAL, TOL * NOTIONAL); // test against trade pricer assertEquals(computedCall, TRADE_PRICER.presentValue(callTrade, RATES_PROVIDER, VOLS).getAmount(USD)); } }
//------------------------------------------------------------------------- public virtual void test_volatility() { for (int i = 0; i < NB_EXPIRY; i++) { double expiryTime = VOLS.relativeTime(TEST_EXPIRY[i]); for (int j = 0; j < NB_STRIKE; ++j) { double volExpected = SMILE_TERM.volatility(expiryTime, TEST_STRIKE[j], FORWARD[i]); double volComputed = VOLS.volatility(CURRENCY_PAIR, TEST_EXPIRY[i], TEST_STRIKE[j], FORWARD[i]); assertEquals(volComputed, volExpected, TOLERANCE); } } }
//------------------------------------------------------------------------- /// <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 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> /// 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)); }
/// <summary> /// Computes the present value sensitivity to the black volatilities used in the pricing. /// <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 sensitivity </returns> public virtual PointSensitivityBuilder presentValueSensitivityModelParamsVolatility(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; 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 vegaAtm = BlackFormulaRepository.vega(forwardRate, strikeRate, timeToExpiry, volAtm); double signedNotional = this.signedNotional(option); PointSensitivityBuilder sensiSmile = PointSensitivityBuilder.none(); for (int i = 0; i < 3; i += 2) { double vegaFwdAtm = BlackFormulaRepository.vega(forwardRate, strikes[i], timeToExpiry, volAtm); vegaAtm -= x[i] * vegaFwdAtm; double vegaFwdSmile = BlackFormulaRepository.vega(forwardRate, strikes[i], timeToExpiry, vols[i]); sensiSmile = sensiSmile.combinedWith(FxOptionSensitivity.of(volatilities.Name, currencyPair, timeToExpiry, strikes[i], forwardRate, ccyCounter, df * signedNotional * x[i] * vegaFwdSmile)); } FxOptionSensitivity sensiAtm = FxOptionSensitivity.of(volatilities.Name, currencyPair, timeToExpiry, strikes[1], forwardRate, ccyCounter, df * signedNotional * vegaAtm); return(sensiAtm.combinedWith(sensiSmile)); }
//------------------------------------------------------------------------- public virtual void test_price_presentValue() { double priceCallOtm = PRICER.price(CALL_OTM, RATES_PROVIDER, VOLS); CurrencyAmount pvCallOtm = PRICER.presentValue(CALL_OTM, RATES_PROVIDER, VOLS); double pricePutOtm = PRICER.price(PUT_OTM, RATES_PROVIDER, VOLS); CurrencyAmount pvPutOtm = PRICER.presentValue(PUT_OTM, RATES_PROVIDER, VOLS); double timeToExpiry = VOLS.relativeTime(EXPIRY); double df = RATES_PROVIDER.discountFactor(USD, PAYMENT_DATE); double forward = PRICER.DiscountingFxSingleProductPricer.forwardFxRate(FX_PRODUCT_HIGH, RATES_PROVIDER).fxRate(CURRENCY_PAIR); double volHigh = SMILE_TERM.volatility(timeToExpiry, STRIKE_RATE_HIGH, forward); double volLow = SMILE_TERM.volatility(timeToExpiry, STRIKE_RATE_LOW, forward); double expectedPriceCallOtm = df * BlackFormulaRepository.price(forward, STRIKE_RATE_HIGH, timeToExpiry, volHigh, true); double expectedPricePutOtm = df * BlackFormulaRepository.price(forward, STRIKE_RATE_LOW, timeToExpiry, volLow, false); double expectedPvCallOtm = -NOTIONAL *df *BlackFormulaRepository.price(forward, STRIKE_RATE_HIGH, timeToExpiry, volHigh, true); double expectedPvPutOtm = -NOTIONAL *df *BlackFormulaRepository.price(forward, STRIKE_RATE_LOW, timeToExpiry, volLow, false); assertEquals(priceCallOtm, expectedPriceCallOtm, TOL); assertEquals(pvCallOtm.Currency, USD); assertEquals(pvCallOtm.Amount, expectedPvCallOtm, NOTIONAL * TOL); assertEquals(pricePutOtm, expectedPricePutOtm, TOL); assertEquals(pvPutOtm.Currency, USD); assertEquals(pvPutOtm.Amount, expectedPvPutOtm, NOTIONAL * TOL); }
//------------------------------------------------------------------------- // bumping a node point at (nodeExpiry, nodeDelta) private double nodeSensitivity(BlackFxOptionSmileVolatilities provider, CurrencyPair pair, ZonedDateTime expiry, double strike, double forward, double nodeExpiry, double nodeDelta) { double strikeMod = provider.CurrencyPair.Equals(pair) ? strike : 1.0 / strike; double forwardMod = provider.CurrencyPair.Equals(pair) ? forward : 1.0 / forward; InterpolatedStrikeSmileDeltaTermStructure smileTerm = (InterpolatedStrikeSmileDeltaTermStructure)provider.Smile; double[] times = smileTerm.Expiries.toArray(); int nTimes = times.Length; SmileDeltaParameters[] volTermUp = new SmileDeltaParameters[nTimes]; SmileDeltaParameters[] volTermDw = new SmileDeltaParameters[nTimes]; int deltaIndex = -1; for (int i = 0; i < nTimes; ++i) { DoubleArray deltas = smileTerm.VolatilityTerm.get(i).Delta; int nDeltas = deltas.size(); int nDeltasTotal = 2 * nDeltas + 1; double[] deltasTotal = new double[nDeltasTotal]; deltasTotal[nDeltas] = 0.5d; for (int j = 0; j < nDeltas; ++j) { deltasTotal[j] = 1d - deltas.get(j); deltasTotal[2 * nDeltas - j] = deltas.get(j); } double[] volsUp = smileTerm.VolatilityTerm.get(i).Volatility.toArray(); double[] volsDw = smileTerm.VolatilityTerm.get(i).Volatility.toArray(); if (Math.Abs(times[i] - nodeExpiry) < TOLERANCE) { for (int j = 0; j < nDeltasTotal; ++j) { if (Math.Abs(deltasTotal[j] - nodeDelta) < TOLERANCE) { deltaIndex = j; volsUp[j] += EPS; volsDw[j] -= EPS; } } } volTermUp[i] = SmileDeltaParameters.of(times[i], deltas, DoubleArray.copyOf(volsUp)); volTermDw[i] = SmileDeltaParameters.of(times[i], deltas, DoubleArray.copyOf(volsDw)); } InterpolatedStrikeSmileDeltaTermStructure smileTermUp = InterpolatedStrikeSmileDeltaTermStructure.of(ImmutableList.copyOf(volTermUp), ACT_365F); InterpolatedStrikeSmileDeltaTermStructure smileTermDw = InterpolatedStrikeSmileDeltaTermStructure.of(ImmutableList.copyOf(volTermDw), ACT_365F); BlackFxOptionSmileVolatilities provUp = BlackFxOptionSmileVolatilities.of(NAME, CURRENCY_PAIR, VAL_DATE_TIME, smileTermUp); BlackFxOptionSmileVolatilities provDw = BlackFxOptionSmileVolatilities.of(NAME, CURRENCY_PAIR, VAL_DATE_TIME, smileTermDw); double volUp = provUp.volatility(pair, expiry, strike, forward); double volDw = provDw.volatility(pair, expiry, strike, forward); double totalSensi = 0.5 * (volUp - volDw) / EPS; double expiryTime = provider.relativeTime(expiry); SmileDeltaParameters singleSmile = smileTerm.smileForExpiry(expiryTime); double[] strikesUp = singleSmile.strike(forwardMod).toArray(); double[] strikesDw = strikesUp.Clone(); double[] vols = singleSmile.Volatility.toArray(); strikesUp[deltaIndex] += EPS; strikesDw[deltaIndex] -= EPS; double volStrikeUp = LINEAR.bind(DoubleArray.ofUnsafe(strikesUp), DoubleArray.ofUnsafe(vols), FLAT, FLAT).interpolate(strikeMod); double volStrikeDw = LINEAR.bind(DoubleArray.ofUnsafe(strikesDw), DoubleArray.ofUnsafe(vols), FLAT, FLAT).interpolate(strikeMod); double sensiStrike = 0.5 * (volStrikeUp - volStrikeDw) / EPS; SmileDeltaParameters singleSmileUp = smileTermUp.smileForExpiry(expiryTime); double strikeUp = singleSmileUp.strike(forwardMod).get(deltaIndex); SmileDeltaParameters singleSmileDw = smileTermDw.smileForExpiry(expiryTime); double strikeDw = singleSmileDw.strike(forwardMod).get(deltaIndex); double sensiVol = 0.5 * (strikeUp - strikeDw) / EPS; return(totalSensi - sensiStrike * sensiVol); }