private static readonly VolatilityFunctionProvider <SabrFormulaData>[] FUNCTIONS = new VolatilityFunctionProvider[] { FUNC_HAGAN }; // other volatility functions to be added

        /// <summary>
        /// Testing C2 continuity.
        /// </summary>
//JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes:
//ORIGINAL LINE: @Test public void smoothnessTest()
        public virtual void smoothnessTest()
        {
            foreach (VolatilityFunctionProvider <SabrFormulaData> func in FUNCTIONS)
            {
                SabrExtrapolationRightFunction extrapolation = SabrExtrapolationRightFunction.of(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, func);
                foreach (PutCall isCall in new PutCall[] { PutCall.CALL, PutCall.PUT })
                {
                    double priceBase = extrapolation.price(CUT_OFF_STRIKE, isCall);
                    double priceUp   = extrapolation.price(CUT_OFF_STRIKE + EPS, isCall);
                    double priceDw   = extrapolation.price(CUT_OFF_STRIKE - EPS, isCall);
                    assertEquals(priceBase, priceUp, EPS);
                    assertEquals(priceBase, priceDw, EPS);
                    double priceUpUp = extrapolation.price(CUT_OFF_STRIKE + 2.0 * EPS, isCall);
                    double priceDwDw = extrapolation.price(CUT_OFF_STRIKE - 2.0 * EPS, isCall);
                    double firstUp   = (-0.5 * priceUpUp + 2.0 * priceUp - 1.5 * priceBase) / EPS;
                    double firstDw   = (-2.0 * priceDw + 0.5 * priceDwDw + 1.5 * priceBase) / EPS;
                    assertEquals(firstDw, firstUp, EPS);
                    // The second derivative values are poorly connected due to finite difference approximation
                    double firstUpUp = 0.5 * (priceUpUp - priceBase) / EPS;
                    double firstDwDw = 0.5 * (priceBase - priceDwDw) / EPS;
                    double secondUp  = (firstUpUp - firstUp) / EPS;
                    double secondDw  = (firstDw - firstDwDw) / EPS;
                    double secondRef = 0.5 * (firstUpUp - firstDwDw) / EPS;
                    assertEquals(secondRef, secondUp, secondRef * 0.15);
                    assertEquals(secondRef, secondDw, secondRef * 0.15);
                }
            }
        }
        /// <summary>
        /// Test small forward.
        /// </summary>
//JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes:
//ORIGINAL LINE: @Test public void smallForwardTest()
        public virtual void smallForwardTest()
        {
            double smallForward = 0.1e-6;
            double smallCutoff  = 0.9e-6;

            foreach (VolatilityFunctionProvider <SabrFormulaData> func in FUNCTIONS)
            {
                SabrExtrapolationRightFunction right = SabrExtrapolationRightFunction.of(smallForward, SABR_DATA, smallCutoff, TIME_TO_EXPIRY, MU, func);
                foreach (PutCall isCall in new PutCall[] { PutCall.CALL, PutCall.PUT })
                {
                    double priceBase = right.price(smallCutoff, isCall);
                    double priceUp   = right.price(smallCutoff + EPS * 0.1, isCall);
                    double priceDw   = right.price(smallCutoff - EPS * 0.1, isCall);
                    assertEquals(priceBase, priceUp, EPS * 10.0);
                    assertEquals(priceBase, priceDw, EPS * 10.0);
                }
            }
        }
        /// <summary>
        /// Tests the price derivative with respect to forward for options in SABR model with extrapolation.
        /// </summary>
        public virtual void priceDerivativeForwardPut()
        {
            SabrExtrapolationRightFunction func = SabrExtrapolationRightFunction.of(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, SabrHaganVolatilityFunctionProvider.DEFAULT);
            double strikeIn  = 0.08;
            double strikeAt  = CUT_OFF_STRIKE;
            double strikeOut = 0.12;
            EuropeanVanillaOption optionIn  = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT);
            EuropeanVanillaOption optionAt  = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT);
            EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT);
            double          shiftF          = 0.000001;
            SabrFormulaData sabrDataFP      = SabrFormulaData.of(ALPHA, BETA, RHO, NU);
            SabrExtrapolationRightFunction sabrExtrapolationFP = SabrExtrapolationRightFunction.of(FORWARD + shiftF, TIME_TO_EXPIRY, sabrDataFP, CUT_OFF_STRIKE, MU);
            // Below cut-off strike
            double priceIn           = func.price(optionIn.Strike, optionIn.PutCall);
            double priceInFP         = sabrExtrapolationFP.price(optionIn.Strike, optionIn.PutCall);
            double priceInDF         = func.priceDerivativeForward(optionIn.Strike, optionIn.PutCall);
            double priceInDFExpected = (priceInFP - priceIn) / shiftF;

            assertEquals(priceInDFExpected, priceInDF, 1E-5);
            // At cut-off strike
            double priceAt           = func.price(optionAt.Strike, optionAt.PutCall);
            double priceAtFP         = sabrExtrapolationFP.price(optionAt.Strike, optionAt.PutCall);
            double priceAtDF         = func.priceDerivativeForward(optionAt.Strike, optionAt.PutCall);
            double priceAtDFExpected = (priceAtFP - priceAt) / shiftF;

            assertEquals(priceAtDFExpected, priceAtDF, 1E-6);
            // Above cut-off strike
            double priceOut           = func.price(optionOut.Strike, optionOut.PutCall);
            double priceOutFP         = sabrExtrapolationFP.price(optionOut.Strike, optionOut.PutCall);
            double priceOutDF         = func.priceDerivativeForward(optionOut.Strike, optionOut.PutCall);
            double priceOutDFExpected = (priceOutFP - priceOut) / shiftF;

            assertEquals(priceOutDFExpected, priceOutDF, 1E-5);
            double[] abc           = func.Parameter;
            double[] abcDF         = func.ParameterDerivativeForward;
            double[] abcFP         = sabrExtrapolationFP.Parameter;
            double[] abcDFExpected = new double[3];
            for (int loopparam = 0; loopparam < 3; loopparam++)
            {
                abcDFExpected[loopparam] = (abcFP[loopparam] - abc[loopparam]) / shiftF;
                assertEquals(1.0, abcDFExpected[loopparam] / abcDF[loopparam], 5E-2);
            }
        }
        /// <summary>
        /// Extrapolator is not calibrated in this case, then the gap may be produced at the cutoff.
        /// </summary>
//JAVA TO C# CONVERTER TODO TASK: Most Java annotations will not have direct .NET equivalent attributes:
//ORIGINAL LINE: @Test public void smallExpiryTest()
        public virtual void smallExpiryTest()
        {
            double smallExpiry = 0.5e-6;

            foreach (VolatilityFunctionProvider <SabrFormulaData> func in FUNCTIONS)
            {
                SabrExtrapolationRightFunction right = SabrExtrapolationRightFunction.of(FORWARD * 0.01, SABR_DATA, CUT_OFF_STRIKE, smallExpiry, MU, func);
                foreach (PutCall isCall in new PutCall[] { PutCall.CALL, PutCall.PUT })
                {
                    double priceBase = right.price(CUT_OFF_STRIKE, isCall);
                    double priceUp   = right.price(CUT_OFF_STRIKE + EPS * 0.1, isCall);
                    double priceDw   = right.price(CUT_OFF_STRIKE - EPS * 0.1, isCall);
                    assertEquals(priceBase, priceUp, EPS);
                    assertEquals(priceBase, priceDw, EPS);
                    assertEquals(right.Parameter[0], -1.0E4, 1.e-12);
                    assertEquals(right.Parameter[1], 0.0, 1.e-12);
                    assertEquals(right.Parameter[2], 0.0, 1.e-12);
                }
            }
        }
        /// <summary>
        /// Tests the price for options in SABR model with extrapolation.
        /// </summary>
        public virtual void priceCloseToExpiry()
        {
            double[] timeToExpiry = new double[] { 1.0 / 365, 0.0 };   // One day and on expiry day.
            double   strikeIn     = 0.08;
            double   strikeAt     = CUT_OFF_STRIKE;
            double   strikeOut    = 0.12;

            for (int loopexp = 0; loopexp < timeToExpiry.Length; loopexp++)
            {
                SabrExtrapolationRightFunction sabrExtra = SabrExtrapolationRightFunction.of(FORWARD, timeToExpiry[loopexp], SABR_DATA, CUT_OFF_STRIKE, MU);
                double volatilityIn    = SABR_FUNCTION.volatility(FORWARD, strikeIn, timeToExpiry[loopexp], SABR_DATA);
                double priceExpectedIn = BlackFormulaRepository.price(FORWARD, strikeIn, timeToExpiry[loopexp], volatilityIn, true);
                double priceIn         = sabrExtra.price(strikeIn, PutCall.CALL);
                assertEquals(priceExpectedIn, priceIn, TOLERANCE_PRICE);
                double volatilityAt    = SABR_FUNCTION.volatility(FORWARD, strikeAt, timeToExpiry[loopexp], SABR_DATA);
                double priceExpectedAt = BlackFormulaRepository.price(FORWARD, strikeAt, timeToExpiry[loopexp], volatilityAt, true);
                double priceAt         = sabrExtra.price(strikeAt, PutCall.CALL);
                assertEquals(priceExpectedAt, priceAt, TOLERANCE_PRICE);
                double priceOut         = sabrExtra.price(strikeOut, PutCall.CALL);
                double priceExpectedOut = 0.0;   // From previous run
                assertEquals(priceExpectedOut, priceOut, TOLERANCE_PRICE);
            }
        }
        /// <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);
        }
        /// <summary>
        /// Tests the price derivative with respect to forward for options in SABR model with extrapolation. Other data.
        /// </summary>
        public virtual void priceDerivativeSABR2()
        {
            double alpha  = 0.06;
            double beta   = 0.5;
            double rho    = 0.0;
            double nu     = 0.3;
            double cutOff = 0.10;
            double mu     = 2.5;
            double strike = 0.15;
            double t      = 2.366105247;
            EuropeanVanillaOption option   = EuropeanVanillaOption.of(strike, t, PutCall.CALL);
            SabrFormulaData       sabrData = SabrFormulaData.of(alpha, beta, rho, nu);
            double forward = 0.0404500579038675;
            SabrExtrapolationRightFunction sabrExtrapolation = SabrExtrapolationRightFunction.of(forward, t, sabrData, cutOff, mu);
            double          shift      = 0.000001;
            SabrFormulaData sabrDataAP = SabrFormulaData.of(alpha + shift, beta, rho, nu);
            SabrFormulaData sabrDataBP = SabrFormulaData.of(alpha, beta + shift, rho, nu);
            SabrFormulaData sabrDataRP = SabrFormulaData.of(alpha, beta, rho + shift, nu);
            SabrFormulaData sabrDataNP = SabrFormulaData.of(alpha, beta, rho, nu + shift);
            SabrExtrapolationRightFunction sabrExtrapolationAP = SabrExtrapolationRightFunction.of(forward, t, sabrDataAP, cutOff, mu);
            SabrExtrapolationRightFunction sabrExtrapolationBP = SabrExtrapolationRightFunction.of(forward, t, sabrDataBP, cutOff, mu);
            SabrExtrapolationRightFunction sabrExtrapolationRP = SabrExtrapolationRightFunction.of(forward, t, sabrDataRP, cutOff, mu);
            SabrExtrapolationRightFunction sabrExtrapolationNP = SabrExtrapolationRightFunction.of(forward, t, sabrDataNP, cutOff, mu);

            // Above cut-off strike
            double[]   abc   = sabrExtrapolation.Parameter;
            double[][] abcDP = sabrExtrapolation.ParameterDerivativeSabr;
//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[][] abcPP = new double[4][3];
            double[][] abcPP = RectangularArrays.ReturnRectangularDoubleArray(4, 3);
            abcPP[0] = sabrExtrapolationAP.Parameter;
            abcPP[1] = sabrExtrapolationBP.Parameter;
            abcPP[2] = sabrExtrapolationRP.Parameter;
            abcPP[3] = sabrExtrapolationNP.Parameter;
//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[][] abcDPExpected = new double[4][3];
            double[][] abcDPExpected = RectangularArrays.ReturnRectangularDoubleArray(4, 3);
            for (int loopparam = 0; loopparam < 4; loopparam++)
            {
                for (int loopabc = 0; loopabc < 3; loopabc++)
                {
                    abcDPExpected[loopparam][loopabc] = (abcPP[loopparam][loopabc] - abc[loopabc]) / shift;
                    assertEquals(1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2);
                }
            }
            double priceOutExpected = sabrExtrapolation.price(option.Strike, option.PutCall);

            double[] priceOutPP = new double[4];
            priceOutPP[0] = sabrExtrapolationAP.price(option.Strike, option.PutCall);
            priceOutPP[1] = sabrExtrapolationBP.price(option.Strike, option.PutCall);
            priceOutPP[2] = sabrExtrapolationRP.price(option.Strike, option.PutCall);
            priceOutPP[3] = sabrExtrapolationNP.price(option.Strike, option.PutCall);
            ValueDerivatives resOut   = sabrExtrapolation.priceAdjointSabr(option.Strike, option.PutCall);
            double           priceOut = resOut.Value;

            double[] priceOutDsabr = resOut.Derivatives.toArray();
            assertEquals(priceOutExpected, priceOut, 1E-5);
            double[] priceOutDsabrExpected = new double[4];
            for (int loopparam = 0; loopparam < 4; loopparam++)
            {
                priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift;
                assertEquals(1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4);
            }
        }
        /// <summary>
        /// Tests the price derivative with respect to forward for options in SABR model with extrapolation.
        /// </summary>
        public virtual void priceDerivativeSabrPut()
        {
            SabrExtrapolationRightFunction func = SabrExtrapolationRightFunction.of(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, SabrHaganVolatilityFunctionProvider.DEFAULT);
            double strikeIn  = 0.08;
            double strikeAt  = CUT_OFF_STRIKE;
            double strikeOut = 0.12;
            EuropeanVanillaOption optionIn  = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT);
            EuropeanVanillaOption optionAt  = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT);
            EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT);
            double          shift           = 0.000001;
            SabrFormulaData sabrDataAP      = SabrFormulaData.of(ALPHA + shift, BETA, RHO, NU);
            SabrFormulaData sabrDataBP      = SabrFormulaData.of(ALPHA, BETA + shift, RHO, NU);
            SabrFormulaData sabrDataRP      = SabrFormulaData.of(ALPHA, BETA, RHO + shift, NU);
            SabrFormulaData sabrDataNP      = SabrFormulaData.of(ALPHA, BETA, RHO, NU + shift);
            SabrExtrapolationRightFunction sabrExtrapolationAP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataAP, CUT_OFF_STRIKE, MU);
            SabrExtrapolationRightFunction sabrExtrapolationBP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataBP, CUT_OFF_STRIKE, MU);
            SabrExtrapolationRightFunction sabrExtrapolationRP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataRP, CUT_OFF_STRIKE, MU);
            SabrExtrapolationRightFunction sabrExtrapolationNP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataNP, CUT_OFF_STRIKE, MU);
            // Below cut-off strike
            double priceInExpected = func.price(optionIn.Strike, optionIn.PutCall);

            double[] priceInPP = new double[4];
            priceInPP[0] = sabrExtrapolationAP.price(optionIn.Strike, optionIn.PutCall);
            priceInPP[1] = sabrExtrapolationBP.price(optionIn.Strike, optionIn.PutCall);
            priceInPP[2] = sabrExtrapolationRP.price(optionIn.Strike, optionIn.PutCall);
            priceInPP[3] = sabrExtrapolationNP.price(optionIn.Strike, optionIn.PutCall);
            ValueDerivatives resIn   = func.priceAdjointSabr(optionIn.Strike, optionIn.PutCall);
            double           priceIn = resIn.Value;

            double[] priceInDsabr = resIn.Derivatives.toArray();
            assertEquals(priceInExpected, priceIn, TOLERANCE_PRICE);
            double[] priceInDsabrExpected = new double[4];
            for (int loopparam = 0; loopparam < 3; loopparam++)
            {
                priceInDsabrExpected[loopparam] = (priceInPP[loopparam] - priceIn) / shift;
                assertEquals(priceInDsabrExpected[loopparam], priceInDsabr[loopparam], 1E-5);
            }
            // At cut-off strike
            double priceAtExpected = func.price(optionAt.Strike, optionAt.PutCall);

            double[] priceAtPP = new double[4];
            priceAtPP[0] = sabrExtrapolationAP.price(optionAt.Strike, optionAt.PutCall);
            priceAtPP[1] = sabrExtrapolationBP.price(optionAt.Strike, optionAt.PutCall);
            priceAtPP[2] = sabrExtrapolationRP.price(optionAt.Strike, optionAt.PutCall);
            priceAtPP[3] = sabrExtrapolationNP.price(optionAt.Strike, optionAt.PutCall);
            ValueDerivatives resAt   = func.priceAdjointSabr(optionAt.Strike, optionAt.PutCall);
            double           priceAt = resAt.Value;

            double[] priceAtDsabr = resAt.Derivatives.toArray();
            assertEquals(priceAtExpected, priceAt, TOLERANCE_PRICE);
            double[] priceAtDsabrExpected = new double[4];
            for (int loopparam = 0; loopparam < 3; loopparam++)
            {
                priceAtDsabrExpected[loopparam] = (priceAtPP[loopparam] - priceAt) / shift;
                assertEquals(priceAtDsabrExpected[loopparam], priceAtDsabr[loopparam], 1E-5);
            }
            // Above cut-off strike
            double priceOutExpected = func.price(optionOut.Strike, optionOut.PutCall);

            double[] priceOutPP = new double[4];
            priceOutPP[0] = sabrExtrapolationAP.price(optionOut.Strike, optionOut.PutCall);
            priceOutPP[1] = sabrExtrapolationBP.price(optionOut.Strike, optionOut.PutCall);
            priceOutPP[2] = sabrExtrapolationRP.price(optionOut.Strike, optionOut.PutCall);
            priceOutPP[3] = sabrExtrapolationNP.price(optionOut.Strike, optionOut.PutCall);
            ValueDerivatives resOut   = func.priceAdjointSabr(optionOut.Strike, optionOut.PutCall);
            double           priceOut = resOut.Value;

            double[] priceOutDsabr = resOut.Derivatives.toArray();
            assertEquals(priceOutExpected, priceOut, TOLERANCE_PRICE);
            double[]   abc   = func.Parameter;
            double[][] abcDP = func.ParameterDerivativeSabr;
//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[][] abcPP = new double[4][3];
            double[][] abcPP = RectangularArrays.ReturnRectangularDoubleArray(4, 3);
            abcPP[0] = sabrExtrapolationAP.Parameter;
            abcPP[1] = sabrExtrapolationBP.Parameter;
            abcPP[2] = sabrExtrapolationRP.Parameter;
            abcPP[3] = sabrExtrapolationNP.Parameter;
//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[][] abcDPExpected = new double[4][3];
            double[][] abcDPExpected = RectangularArrays.ReturnRectangularDoubleArray(4, 3);
            for (int loopparam = 0; loopparam < 4; loopparam++)
            {
                for (int loopabc = 0; loopabc < 3; loopabc++)
                {
                    abcDPExpected[loopparam][loopabc] = (abcPP[loopparam][loopabc] - abc[loopabc]) / shift;
                    assertEquals(1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2);
                }
            }
            double[] priceOutDsabrExpected = new double[4];
            for (int loopparam = 0; loopparam < 4; loopparam++)
            {
                priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift;
                assertEquals(1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4);
            }
        }