/// <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>
        /// 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));
        }
        //-------------------------------------------------------------------------
        private void validateData(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData data)
        {
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.getTime(data.NumberOfSteps), volatilities.relativeTime(underlyingOption.Expiry), SMALL), "time to expiry mismatch between pricing option and trinomial tree data");
            ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.Spot, ratesProvider.fxRate(underlyingOption.Underlying.CurrencyPair), SMALL), "today's FX rate mismatch between rates provider and trinomial tree data");
        }
        /// <summary>
        /// Calculates the present value of the FX barrier option product.
        /// <para>
        /// The present value of the product is the value on the valuation date.
        /// It is expressed in the counter currency.
        /// </para>
        /// <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 present value of the product </returns>
        public virtual CurrencyAmount presentValue(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData treeData)
        {
            double price = this.price(option, ratesProvider, volatilities, treeData);
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            return(CurrencyAmount.of(underlyingOption.CounterCurrency, signedNotional(underlyingOption) * price));
        }
        /// <summary>
        /// Calculates the present value sensitivity of the FX barrier option product.
        /// <para>
        /// The present value sensitivity of the product is the sensitivity of <seealso cref="#presentValue"/> to
        /// the underlying curve parameters.
        /// </para>
        /// <para>
        /// The sensitivity is computed by bump and re-price.
        ///
        /// </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="baseTreeData">  the trinomial tree data </param>
        /// <returns> the present value of the product </returns>
        public virtual CurrencyParameterSensitivities presentValueSensitivityRates(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData baseTreeData)
        {
            ArgChecker.isTrue(baseTreeData.NumberOfSteps == calibrator.NumberOfSteps, "the number of steps mismatch between pricer and trinomial tree data");
            double                         shift            = 1.0e-5;
            CurrencyAmount                 pvBase           = presentValue(option, ratesProvider, volatilities, baseTreeData);
            ResolvedFxVanillaOption        underlyingOption = option.UnderlyingOption;
            ResolvedFxSingle               underlyingFx     = underlyingOption.Underlying;
            CurrencyPair                   currencyPair     = underlyingFx.CurrencyPair;
            ImmutableRatesProvider         immRatesProvider = ratesProvider.toImmutableRatesProvider();
            ImmutableMap <Currency, Curve> baseCurves       = immRatesProvider.DiscountCurves;
            CurrencyParameterSensitivities result           = CurrencyParameterSensitivities.empty();

            foreach (KeyValuePair <Currency, Curve> entry in baseCurves.entrySet())
            {
                if (currencyPair.contains(entry.Key))
                {
                    Curve       curve       = entry.Value;
                    int         nParams     = curve.ParameterCount;
                    DoubleArray sensitivity = DoubleArray.of(nParams, i =>
                    {
                        Curve dscBumped = curve.withParameter(i, curve.getParameter(i) + shift);
                        IDictionary <Currency, Curve> mapBumped = new Dictionary <Currency, Curve>(baseCurves);
                        mapBumped[entry.Key] = dscBumped;
                        ImmutableRatesProvider providerDscBumped = immRatesProvider.toBuilder().discountCurves(mapBumped).build();
                        double pvBumped = presentValue(option, providerDscBumped, volatilities).Amount;
                        return((pvBumped - pvBase.Amount) / shift);
                    });
                    result = result.combinedWith(curve.createParameterSensitivity(pvBase.Currency, sensitivity));
                }
            }
            return(result);
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the present value of the FX barrier option trade.
        /// <para>
        /// The present value of the trade is the value on the valuation date.
        /// </para>
        /// <para>
        /// The trinomial tree is first calibrated to Black volatilities,
        /// then the price is computed based on the calibrated tree.
        ///
        /// </para>
        /// </summary>
        /// <param name="trade">  the option trade </param>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="volatilities">  the Black volatility provider </param>
        /// <returns> the present value of the trade </returns>
        public virtual MultiCurrencyAmount presentValue(ResolvedFxSingleBarrierOptionTrade trade, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxSingleBarrierOption product = trade.Product;
            CurrencyAmount pvProduct = productPricer.presentValue(product, ratesProvider, volatilities);
            Payment        premium   = trade.Premium;
            CurrencyAmount pvPremium = paymentPricer.presentValue(premium, ratesProvider);

            return(MultiCurrencyAmount.of(pvProduct, pvPremium));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the price of the FX barrier 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(ResolvedFxSingleBarrierOption, RatesProvider, BlackFxOptionVolatilities) presentValue"/>
        /// for scaling and currency.
        /// </para>
        /// <para>
        /// The trinomial tree is first calibrated to Black volatilities,
        /// then the price is computed based on the calibrated tree.
        ///
        /// </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(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            RecombiningTrinomialTreeData treeData = calibrator.calibrateTrinomialTree(option.UnderlyingOption, ratesProvider, volatilities);

            return(price(option, ratesProvider, volatilities, treeData));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calibrate trinomial tree to Black volatilities by using a vanilla option.
        /// <para>
        /// {@code ResolvedFxVanillaOption} is typically the underlying option of an exotic instrument to price using the
        /// calibrated tree, and is used to ensure that the grid points properly cover the lifetime of the target option.
        ///
        /// </para>
        /// </summary>
        /// <param name="option">  the vanilla option </param>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="volatilities">  the Black volatility provider </param>
        /// <returns> the trinomial tree data </returns>
        public virtual RecombiningTrinomialTreeData calibrateTrinomialTree(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            double       timeToExpiry = volatilities.relativeTime(option.Expiry);
            CurrencyPair currencyPair = option.Underlying.CurrencyPair;

            return(calibrateTrinomialTree(timeToExpiry, currencyPair, ratesProvider, volatilities));
        }
        /// <summary>
        /// Calibrate trinomial tree to Black volatilities.
        /// <para>
        /// {@code timeToExpiry} determines the coverage of the resulting trinomial tree.
        /// Thus this should match the time to expiry of the target instrument to price using the calibrated tree.
        ///
        /// </para>
        /// </summary>
        /// <param name="timeToExpiry">  the time to expiry </param>
        /// <param name="currencyPair">  the currency pair </param>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="volatilities">  the Black volatility provider </param>
        /// <returns> the trinomial tree data </returns>
        public virtual RecombiningTrinomialTreeData calibrateTrinomialTree(double timeToExpiry, CurrencyPair currencyPair, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            validate(ratesProvider, volatilities);
            if (timeToExpiry <= 0d)
            {
                throw new System.ArgumentException("option expired");
            }
            Currency        ccyBase                = currencyPair.Base;
            Currency        ccyCounter             = currencyPair.Counter;
            double          todayFx                = ratesProvider.fxRate(currencyPair);
            DiscountFactors baseDiscountFactors    = ratesProvider.discountFactors(ccyBase);
            DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter);

            System.Func <double, double> interestRate = (double?t) =>
            {
                return(counterDiscountFactors.zeroRate(t.Value));
            };
            System.Func <double, double> dividendRate = (double?t) =>
            {
                return(baseDiscountFactors.zeroRate(t.Value));
            };
            System.Func <DoublesPair, double> impliedVolSurface = (DoublesPair tk) =>
            {
                double dfBase    = baseDiscountFactors.discountFactor(tk.First);
                double dfCounter = counterDiscountFactors.discountFactor(tk.First);
                double forward   = todayFx * dfBase / dfCounter;
                return(volatilities.volatility(currencyPair, tk.First, tk.Second, forward));
            };
            ImpliedTrinomialTreeLocalVolatilityCalculator localVol = new ImpliedTrinomialTreeLocalVolatilityCalculator(nSteps, timeToExpiry);

            return(localVol.calibrateImpliedVolatility(impliedVolSurface, todayFx, interestRate, dividendRate));
        }
        //-------------------------------------------------------------------------
        //  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)));
        }
 //-------------------------------------------------------------------------
 private void validate(RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
 {
     ArgChecker.isTrue(ratesProvider.ValuationDate.isEqual(volatilities.ValuationDateTime.toLocalDate()), "Volatility and rate data must be for the same date");
 }
        /// <summary>
        /// Calculates the theta of the FX barrier option product.
        /// <para>
        /// The theta is the negative of the first derivative of <seealso cref="#price"/> with respect to time parameter.
        ///
        /// </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(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ValueDerivatives priceDerivatives = this.priceDerivatives(option, ratesProvider, volatilities);

            return(-priceDerivatives.getDerivative(5));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Computes the present value sensitivity to the black volatility used in the pricing.
        /// <para>
        /// The result is a single sensitivity to the volatility used. This is also called Black vega.
        ///
        /// </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(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            if (volatilities.relativeTime(underlyingOption.Expiry) <= 0d)
            {
                return(PointSensitivityBuilder.none());
            }
            ValueDerivatives priceDerivatives = this.priceDerivatives(option, ratesProvider, volatilities);
            ResolvedFxSingle underlyingFx     = underlyingOption.Underlying;
            CurrencyPair     currencyPair     = underlyingFx.CurrencyPair;
            Currency         ccyBase          = currencyPair.Base;
            Currency         ccyCounter       = currencyPair.Counter;
            double           dfBase           = ratesProvider.discountFactor(ccyBase, underlyingFx.PaymentDate);
            double           dfCounter        = ratesProvider.discountFactor(ccyCounter, underlyingFx.PaymentDate);
            double           todayFx          = ratesProvider.fxRate(currencyPair);
            double           forward          = todayFx * dfBase / dfCounter;

            return(FxOptionSensitivity.of(volatilities.Name, currencyPair, volatilities.relativeTime(underlyingOption.Expiry), underlyingOption.Strike, forward, ccyCounter, priceDerivatives.getDerivative(4) * signedNotional(underlyingOption)));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the present value gamma of the FX barrier option product.
        /// <para>
        /// The present value gamma is the second derivative of <seealso cref="#presentValue"/> 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 present value gamma of the product </returns>
        public virtual CurrencyAmount presentValueGamma(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            double gamma = this.gamma(option, ratesProvider, volatilities);
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            return(CurrencyAmount.of(underlyingOption.CounterCurrency, signedNotional(underlyingOption) * gamma));
        }
        //-------------------------------------------------------------------------
        /// <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, BlackFxOptionVolatilities volatilities)
        {
            CurrencyPair   strikePair     = option.Underlying.CurrencyPair;
            double         price          = this.price(option, ratesProvider, volatilities);
            double         delta          = this.delta(option, ratesProvider, volatilities);
            double         spot           = ratesProvider.fxRate(strikePair);
            double         signedNotional = this.signedNotional(option);
            CurrencyAmount domestic       = CurrencyAmount.of(strikePair.Counter, (price - delta * spot) * signedNotional);
            CurrencyAmount foreign        = CurrencyAmount.of(strikePair.Base, delta * signedNotional);

            return(MultiCurrencyAmount.of(domestic, foreign));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the delta of the foreign exchange vanilla 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(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxSingle underlying             = option.Underlying;
            double           fwdDelta               = undiscountedDelta(option, ratesProvider, volatilities);
            double           discountFactor         = ratesProvider.discountFactor(option.CounterCurrency, underlying.PaymentDate);
            double           fwdRateSpotSensitivity = fxPricer.forwardFxRateSpotSensitivity(option.PutCall.Call ? underlying : underlying.inverse(), ratesProvider);

            return(fwdDelta * discountFactor * fwdRateSpotSensitivity);
        }
        //-------------------------------------------------------------------------
        /// <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, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxSingle underlying     = option.Underlying;
            double           forwardPrice   = undiscountedPrice(option, ratesProvider, volatilities);
            double           discountFactor = ratesProvider.discountFactor(option.CounterCurrency, underlying.PaymentDate);

            return(discountFactor * forwardPrice);
        }
        /// <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 volatility is 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 PointSensitivities presentValueSensitivityRatesStickyStrike(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            if (volatilities.relativeTime(option.Expiry) < 0d)
            {
                return(PointSensitivities.empty());
            }
            ResolvedFxSingle        underlying     = option.Underlying;
            double                  fwdDelta       = undiscountedDelta(option, ratesProvider, volatilities);
            double                  discountFactor = ratesProvider.discountFactor(option.CounterCurrency, underlying.PaymentDate);
            double                  notional       = signedNotional(option);
            PointSensitivityBuilder fwdSensi       = fxPricer.forwardFxRatePointSensitivity(option.PutCall.Call ? underlying : underlying.inverse(), ratesProvider).multipliedBy(notional * discountFactor * fwdDelta);
            double                  fwdPrice       = undiscountedPrice(option, ratesProvider, volatilities);
            PointSensitivityBuilder dscSensi       = ratesProvider.discountFactors(option.CounterCurrency).zeroRatePointSensitivity(underlying.PaymentDate).multipliedBy(notional * fwdPrice);

            return(fwdSensi.combinedWith(dscSensi).build().convertedTo(option.CounterCurrency, ratesProvider));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the present value sensitivity of the FX barrier option trade.
        /// <para>
        /// The present value sensitivity of the trade is the sensitivity of the present value to
        /// the underlying curves.
        /// </para>
        /// <para>
        /// The sensitivity is computed by bump and re-price, returning <seealso cref="CurrencyParameterSensitivities"/>,
        /// not <seealso cref="PointSensitivities"/>.
        /// </para>
        /// <para>
        /// The trinomial tree is first calibrated to Black volatilities,
        /// then the price is computed based on the calibrated tree.
        ///
        /// </para>
        /// </summary>
        /// <param name="trade">  the option trade </param>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="volatilities">  the Black volatility provider </param>
        /// <returns> the present value curve sensitivity of the trade </returns>
        public virtual CurrencyParameterSensitivities presentValueSensitivityRates(ResolvedFxSingleBarrierOptionTrade trade, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxSingleBarrierOption  product     = trade.Product;
            CurrencyParameterSensitivities sensProduct = productPricer.presentValueSensitivityRates(product, ratesProvider, volatilities);
            Payment premium = trade.Premium;
            PointSensitivityBuilder        pvcsPremium = paymentPricer.presentValueSensitivity(premium, ratesProvider);
            CurrencyParameterSensitivities sensPremium = ratesProvider.parameterSensitivity(pvcsPremium.build());

            return(sensProduct.combinedWith(sensPremium));
        }
        // the delta without discounting
        private double undiscountedDelta(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;
            bool             isCall      = option.PutCall.Call;

            if (timeToExpiry == 0d)
            {
                return(isCall ? (forwardRate > strikeRate ? 1d : 0d) : (strikeRate > forwardRate ? -1d : 0d));
            }
            double volatility = volatilities.volatility(strikePair, option.Expiry, strikeRate, forwardRate);

            return(BlackFormulaRepository.delta(forwardRate, strikeRate, timeToExpiry, volatility, isCall));
        }
 /// <summary>
 /// Calculates the price of the FX barrier 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(ResolvedFxSingleBarrierOption, RatesProvider, BlackFxOptionVolatilities, RecombiningTrinomialTreeData) presnetValue"/>
 /// for scaling and currency.
 /// </para>
 /// <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 price of the product </returns>
 public virtual double price(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities, RecombiningTrinomialTreeData treeData)
 {
     return(priceDerivatives(option, ratesProvider, volatilities, treeData).Value);
 }
        /// <summary>
        /// Computes the present value sensitivity to the black volatility used in the pricing.
        /// <para>
        /// The result is a single sensitivity to the volatility used.
        ///
        /// </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, BlackFxOptionVolatilities volatilities)
        {
            if (volatilities.relativeTime(option.Expiry) <= 0d)
            {
                return(PointSensitivityBuilder.none());
            }
            ResolvedFxSingle underlying = option.Underlying;
            FxRate           forward    = fxPricer.forwardFxRate(underlying, ratesProvider);
            CurrencyPair     strikePair = underlying.CurrencyPair;
            CurrencyAmount   valueVega  = presentValueVega(option, ratesProvider, volatilities);

            return(FxOptionSensitivity.of(volatilities.Name, strikePair, volatilities.relativeTime(option.Expiry), option.Strike, forward.fxRate(strikePair), valueVega.Currency, valueVega.Amount));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the present value sensitivity of the FX barrier option product.
        /// <para>
        /// The present value sensitivity of the product is the sensitivity of <seealso cref="#presentValue"/> to
        /// the underlying curve parameters.
        /// </para>
        /// <para>
        /// The sensitivity is computed by bump and re-price.
        ///
        /// </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 of the product </returns>
        public virtual CurrencyParameterSensitivities presentValueSensitivityRates(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            RecombiningTrinomialTreeData baseTreeData = calibrator.calibrateTrinomialTree(option.UnderlyingOption, ratesProvider, volatilities);

            return(presentValueSensitivityRates(option, ratesProvider, volatilities, baseTreeData));
        }
        //-------------------------------------------------------------------------
        /// <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 currency exposure of the FX barrier option product.
        /// <para>
        /// The trinomial tree is first calibrated to Black volatilities,
        /// then the price is computed based on the calibrated tree.
        ///
        /// </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 currency exposure </returns>
        public virtual MultiCurrencyAmount currencyExposure(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            RecombiningTrinomialTreeData treeData = calibrator.calibrateTrinomialTree(option.UnderlyingOption, ratesProvider, volatilities);

            return(currencyExposure(option, ratesProvider, volatilities, treeData));
        }
        /// <summary>
        /// Calculates the present value theta of the foreign exchange vanilla option product.
        /// <para>
        /// The present value theta is the negative of the first derivative of <seealso cref="#presentValue"/> with time parameter
        /// in Black formula, i.e., the driftless theta of the present value.
        ///
        /// </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 vega of the product </returns>
        public virtual CurrencyAmount presentValueTheta(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            double theta = this.theta(option, ratesProvider, volatilities);

            return(CurrencyAmount.of(option.CounterCurrency, signedNotional(option) * theta));
        }
        //-------------------------------------------------------------------------
        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);
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the implied Black volatility 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 implied volatility of the product </returns>
        /// <exception cref="IllegalArgumentException"> if the option has expired </exception>
        public virtual double impliedVolatility(ResolvedFxVanillaOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            double timeToExpiry = volatilities.relativeTime(option.Expiry);

            if (timeToExpiry <= 0d)
            {
                throw new System.ArgumentException("valuation is after option's expiry.");
            }
            FxRate       forward    = fxPricer.forwardFxRate(option.Underlying, ratesProvider);
            CurrencyPair strikePair = option.Underlying.CurrencyPair;

            return(volatilities.volatility(strikePair, option.Expiry, option.Strike, forward.fxRate(strikePair)));
        }
 private void validate(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
 {
     ArgChecker.isTrue(option.Barrier is SimpleConstantContinuousBarrier, "barrier should be SimpleConstantContinuousBarrier");
     ArgChecker.isTrue(ratesProvider.ValuationDate.isEqual(volatilities.ValuationDateTime.toLocalDate()), "Volatility and rate data must be for the same date");
 }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Calculates the present value sensitivity of the FX barrier option product.
        /// <para>
        /// The present value sensitivity of the product is the sensitivity of <seealso cref="#presentValue"/> to
        /// the underlying curves.
        /// </para>
        /// <para>
        /// The volatility is fixed in this sensitivity computation, i.e., sticky-strike.
        ///
        /// </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(ResolvedFxSingleBarrierOption option, RatesProvider ratesProvider, BlackFxOptionVolatilities volatilities)
        {
            ResolvedFxVanillaOption underlyingOption = option.UnderlyingOption;

            if (volatilities.relativeTime(underlyingOption.Expiry) <= 0d)
            {
                return(PointSensitivityBuilder.none());
            }
            ValueDerivatives    priceDerivatives    = this.priceDerivatives(option, ratesProvider, volatilities);
            ResolvedFxSingle    underlyingFx        = underlyingOption.Underlying;
            CurrencyPair        currencyPair        = underlyingFx.CurrencyPair;
            double              signedNotional      = this.signedNotional(underlyingOption);
            double              counterYearFraction = ratesProvider.discountFactors(currencyPair.Counter).relativeYearFraction(underlyingFx.PaymentDate);
            ZeroRateSensitivity counterSensi        = ZeroRateSensitivity.of(currencyPair.Counter, counterYearFraction, signedNotional * (priceDerivatives.getDerivative(2) + priceDerivatives.getDerivative(3)));
            double              baseYearFraction    = ratesProvider.discountFactors(currencyPair.Base).relativeYearFraction(underlyingFx.PaymentDate);
            ZeroRateSensitivity baseSensi           = ZeroRateSensitivity.of(currencyPair.Base, baseYearFraction, currencyPair.Counter, -priceDerivatives.getDerivative(3) * signedNotional);

            return(counterSensi.combinedWith(baseSensi));
        }