/// <summary>
        /// Calibrate a single curve to 4 points. Use the resulting calibrated curves as starting point of the computation
        /// of a Jacobian. Compare the direct Jacobian and the one reconstructed from trades.
        /// </summary>
        public virtual void direct_one_curve()
        {
            /* Create trades */
            IList <ResolvedTrade> trades    = new List <ResolvedTrade>();
            IList <LocalDate>     nodeDates = new List <LocalDate>();

            for (int looptenor = 0; looptenor < TENORS_STD_1.Length; looptenor++)
            {
                ResolvedSwapTrade t0   = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA);
                double            rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_SINGLE_CALIBRATED);
                ResolvedSwapTrade t    = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA);
                nodeDates.Add(t.Product.EndDate);
                trades.Add(t);
            }
            /* Par rate sensitivity */
            System.Func <ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) => MULTICURVE_EUR_SINGLE_CALIBRATED.parameterSensitivity(PRICER_SWAP_PRODUCT.parRateSensitivity(((ResolvedSwapTrade)t).Product, MULTICURVE_EUR_SINGLE_CALIBRATED).build());
            DoubleMatrix jiComputed = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(LIST_CURVE_NAMES_1, trades, sensitivityFunction);
            DoubleMatrix jiExpected = MULTICURVE_EUR_SINGLE_CALIBRATED.findData(EUR_SINGLE_NAME).get().Metadata.findInfo(CurveInfoType.JACOBIAN).get().JacobianMatrix;

            /* Comparison */
            assertEquals(jiComputed.rowCount(), jiExpected.rowCount());
            assertEquals(jiComputed.columnCount(), jiExpected.columnCount());
            for (int i = 0; i < jiComputed.rowCount(); i++)
            {
                for (int j = 0; j < jiComputed.columnCount(); j++)
                {
                    assertEquals(jiComputed.get(i, j), jiExpected.get(i, j), TOLERANCE_JAC);
                }
            }
        }
        /// <summary>
        /// Start from a generic zero-coupon curve. Compute the (inverse) Jacobian matrix using linear projection to a small
        /// number of points and the Jacobian utility. Compare the direct Jacobian obtained by calibrating a curve
        /// based on the trades with market quotes computed from the zero-coupon curve.
        /// </summary>
        public virtual void with_rebucketing_one_curve()
        {
            /* Create trades */
            IList <ResolvedTrade> trades    = new List <ResolvedTrade>();
            IList <LocalDate>     nodeDates = new List <LocalDate>();

            double[] marketQuotes = new double[TENORS_STD_1.Length];
            for (int looptenor = 0; looptenor < TENORS_STD_1.Length; looptenor++)
            {
                ResolvedSwapTrade t0 = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA);
                marketQuotes[looptenor] = MARKET_QUOTE.value(t0, MULTICURVE_EUR_SINGLE_INPUT);
                ResolvedSwapTrade t = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, marketQuotes[looptenor], REF_DATA).resolve(REF_DATA);
                nodeDates.Add(t.Product.EndDate);
                trades.Add(t);
            }
            System.Func <ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) => CurveSensitivityUtils.linearRebucketing(MULTICURVE_EUR_SINGLE_INPUT.parameterSensitivity(PRICER_SWAP_PRODUCT.parRateSensitivity(((ResolvedSwapTrade)t).Product, MULTICURVE_EUR_SINGLE_INPUT).build()), nodeDates, VALUATION_DATE);

            /* Market quotes for comparison */
            IDictionary <QuoteId, double> mqCmp = new Dictionary <QuoteId, double>();

            for (int looptenor = 0; looptenor < TENORS_STD_1.Length; looptenor++)
            {
                mqCmp[QuoteId.of(StandardId.of(OG_TICKER, TICKERS_STD_1[looptenor]))] = marketQuotes[looptenor];
            }
            ImmutableMarketData marketQuotesObject = ImmutableMarketData.of(VALUATION_DATE, mqCmp);
            RatesProvider       multicurveCmp      = CALIBRATOR.calibrate(GROUPS_IN_1, marketQuotesObject, REF_DATA);

            /* Comparison */
            DoubleMatrix jiComputed = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(LIST_CURVE_NAMES_1, trades, sensitivityFunction);
            DoubleMatrix jiExpected = multicurveCmp.findData(EUR_SINGLE_NAME).get().Metadata.findInfo(CurveInfoType.JACOBIAN).get().JacobianMatrix;

            assertEquals(jiComputed.rowCount(), jiExpected.rowCount());
            assertEquals(jiComputed.columnCount(), jiExpected.columnCount());
            for (int i = 0; i < jiComputed.rowCount(); i++)
            {
                for (int j = 0; j < jiComputed.columnCount(); j++)
                {
                    assertEquals(jiComputed.get(i, j), jiExpected.get(i, j), TOLERANCE_JAC_APPROX);
                    // The comparison is not perfect due to the incoherences introduced by the re-bucketing
                }
            }
        }
        /// <summary>
        /// Calibrate a single curve to 4 points. Use the resulting calibrated curves as starting point of the computation
        /// of a Jacobian. Compare the direct Jacobian and the one reconstructed from trades.
        /// </summary>
        public virtual void direct_two_curves()
        {
            JacobianCalibrationMatrix          jiObject = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_DSCON_OIS).get().Metadata.findInfo(CurveInfoType.JACOBIAN).get();
            ImmutableList <CurveParameterSize> order    = jiObject.Order; // To obtain the order of the curves in the jacobian

            /* Create trades */
            IList <ResolvedTrade> tradesDsc = new List <ResolvedTrade>();

            for (int looptenor = 0; looptenor < TENORS_STD_2_OIS.Length; looptenor++)
            {
                ResolvedSwapTrade t0   = EUR_FIXED_1Y_EONIA_OIS.createTrade(VALUATION_DATE, TENORS_STD_2_OIS[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA);
                double            rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_2_CALIBRATED);
                ResolvedSwapTrade t    = EUR_FIXED_1Y_EONIA_OIS.createTrade(VALUATION_DATE, TENORS_STD_2_OIS[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA);
                tradesDsc.Add(t);
            }
            IList <ResolvedTrade> tradesE3 = new List <ResolvedTrade>();
            // Fixing
            IborFixingDepositConvention    c    = IborFixingDepositConvention.of(EUR_EURIBOR_6M);
            ResolvedIborFixingDepositTrade fix0 = c.createTrade(VALUATION_DATE, EUR_EURIBOR_6M.Tenor.Period, BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA);
            double rateFixing = MARKET_QUOTE.value(fix0, MULTICURVE_EUR_2_CALIBRATED);
            ResolvedIborFixingDepositTrade fix = c.createTrade(VALUATION_DATE, EUR_EURIBOR_6M.Tenor.Period, BuySell.BUY, 1.0, rateFixing, REF_DATA).resolve(REF_DATA);

            tradesE3.Add(fix);
            // IRS
            for (int looptenor = 0; looptenor < TENORS_STD_2_IRS.Length; looptenor++)
            {
                ResolvedSwapTrade t0   = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_2_IRS[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA);
                double            rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_2_CALIBRATED);
                ResolvedSwapTrade t    = EUR_FIXED_1Y_EURIBOR_6M.createTrade(VALUATION_DATE, TENORS_STD_2_IRS[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA);
                tradesE3.Add(t);
            }
            IList <ResolvedTrade> trades = new List <ResolvedTrade>();

            if (order.get(0).Name.Equals(EUR_DSCON_OIS))
            {
                ((IList <ResolvedTrade>)trades).AddRange(tradesDsc);
                ((IList <ResolvedTrade>)trades).AddRange(tradesE3);
            }
            else
            {
                ((IList <ResolvedTrade>)trades).AddRange(tradesE3);
                ((IList <ResolvedTrade>)trades).AddRange(tradesDsc);
            }
            /* Par rate sensitivity */
            System.Func <ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) => MULTICURVE_EUR_2_CALIBRATED.parameterSensitivity((t is ResolvedSwapTrade) ? PRICER_SWAP_PRODUCT.parRateSensitivity(((ResolvedSwapTrade)t).Product, MULTICURVE_EUR_2_CALIBRATED).build() : PRICER_IBORFIX_PRODUCT.parRateSensitivity(((ResolvedIborFixingDepositTrade)t).Product, MULTICURVE_EUR_2_CALIBRATED));
            DoubleMatrix jiComputed    = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(order, trades, sensitivityFunction);
            DoubleMatrix jiExpectedDsc = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_DSCON_OIS).get().Metadata.getInfo(CurveInfoType.JACOBIAN).JacobianMatrix;
            DoubleMatrix jiExpectedE3  = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_EURIBOR6M_IRS).get().Metadata.getInfo(CurveInfoType.JACOBIAN).JacobianMatrix;

            /* Comparison */
            assertEquals(jiComputed.rowCount(), jiExpectedDsc.rowCount() + jiExpectedE3.rowCount());
            assertEquals(jiComputed.columnCount(), jiExpectedDsc.columnCount());
            assertEquals(jiComputed.columnCount(), jiExpectedE3.columnCount());
            int shiftDsc = order.get(0).Name.Equals(EUR_DSCON_OIS) ? 0 : jiExpectedE3.rowCount();

            for (int i = 0; i < jiExpectedDsc.rowCount(); i++)
            {
                for (int j = 0; j < jiExpectedDsc.columnCount(); j++)
                {
                    assertEquals(jiComputed.get(i + shiftDsc, j), jiExpectedDsc.get(i, j), TOLERANCE_JAC);
                }
            }
            int shiftE3 = order.get(0).Name.Equals(EUR_DSCON_OIS) ? jiExpectedDsc.rowCount() : 0;

            for (int i = 0; i < jiExpectedE3.rowCount(); i++)
            {
                for (int j = 0; j < jiExpectedDsc.columnCount(); j++)
                {
                    assertEquals(jiComputed.get(i + shiftE3, j), jiExpectedE3.get(i, j), TOLERANCE_JAC);
                }
            }
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Runs the calibration of SABR on swaptions and print on the console the present value, bucketed PV01 and
        /// the bucketed Vega of a 18M x 4Y swaption.
        /// </summary>
        /// <param name="args">  -s to use the spares data </param>
        public static void Main(string[] args)
        {
            long start, end;

            // Swaption description
            BuySell          payer            = BuySell.BUY;
            Period           expiry           = Period.ofMonths(18);
            double           notional         = 1_000_000;
            double           strike           = 0.0100;
            Tenor            tenor            = Tenor.TENOR_4Y;
            LocalDate        expiryDate       = EUR_FIXED_1Y_EURIBOR_6M.FloatingLeg.StartDateBusinessDayAdjustment.adjust(CALIBRATION_DATE.plus(expiry), REF_DATA);
            SwapTrade        underlying       = EUR_FIXED_1Y_EURIBOR_6M.createTrade(expiryDate, tenor, payer, notional, strike, REF_DATA);
            Swaption         swaption         = Swaption.builder().expiryDate(AdjustableDate.of(expiryDate)).expiryTime(LocalTime.of(11, 0x0)).expiryZone(ZoneId.of("Europe/Berlin")).underlying(underlying.Product).longShort(LongShort.LONG).swaptionSettlement(PhysicalSwaptionSettlement.DEFAULT).build();
            ResolvedSwaption resolvedSwaption = swaption.resolve(REF_DATA);

            // select data
            TenorRawOptionData data = DATA_FULL;

            if (args.Length > 0)
            {
                if (args[0].Equals("-s"))
                {
                    data = DATA_SPARSE;
                }
            }

            start = DateTimeHelper.CurrentUnixTimeMillis();
            // Curve calibration
            RatesProvider multicurve = CALIBRATOR.calibrate(CONFIGS, MARKET_QUOTES, REF_DATA);

            end = DateTimeHelper.CurrentUnixTimeMillis();
            Console.WriteLine("Curve calibration time: " + (end - start) + " ms.");

            // SABR calibration
            start = DateTimeHelper.CurrentUnixTimeMillis();
            double          beta                    = 0.50;
            SurfaceMetadata betaMetadata            = DefaultSurfaceMetadata.builder().xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).zValueType(ValueType.SABR_BETA).surfaceName("Beta").build();
            Surface         betaSurface             = ConstantSurface.of(betaMetadata, beta);
            double          shift                   = 0.0300;
            Surface         shiftSurface            = ConstantSurface.of("SABR-Shift", shift);
            SabrParametersSwaptionVolatilities sabr = SABR_CALIBRATION.calibrateWithFixedBetaAndShift(DEFINITION, CALIBRATION_TIME, data, multicurve, betaSurface, shiftSurface);

            end = DateTimeHelper.CurrentUnixTimeMillis();
            Console.WriteLine("SABR calibration time: " + (end - start) + " ms.");

            // Price and risk
            Console.WriteLine("Risk measures: ");
            start = DateTimeHelper.CurrentUnixTimeMillis();
            CurrencyAmount pv = SWAPTION_PRICER.presentValue(resolvedSwaption, multicurve, sabr);

            Console.WriteLine("  |-> PV: " + pv.ToString());

            PointSensitivities             deltaPts      = SWAPTION_PRICER.presentValueSensitivityRatesStickyModel(resolvedSwaption, multicurve, sabr).build();
            CurrencyParameterSensitivities deltaBucketed = multicurve.parameterSensitivity(deltaPts);

            Console.WriteLine("  |-> Delta bucketed: " + deltaBucketed.ToString());

            PointSensitivities vegaPts = SWAPTION_PRICER.presentValueSensitivityModelParamsSabr(resolvedSwaption, multicurve, sabr).build();

            Console.WriteLine("  |-> Vega point: " + vegaPts.ToString());

            CurrencyParameterSensitivities vegaBucketed = sabr.parameterSensitivity(vegaPts);

            for (int i = 0; i < vegaBucketed.size(); i++)
            {
                Console.WriteLine("  |-> Vega bucketed: " + vegaBucketed.Sensitivities.get(i));
            }

            end = DateTimeHelper.CurrentUnixTimeMillis();
            Console.WriteLine("PV and risk time: " + (end - start) + " ms.");
        }