Exemple #1
0
 /**
  * Returns a periodic frequency backed by a period of years.
  *
  * @param years the number of years
  * @return the periodic frequency
  * @throws IllegalArgumentException if years is negative, zero or over 1,000
  */
 public static Frequency ofYears(int years) {
   if (years > MAX_YEARS) {
     throw new IllegalArgumentException("Years must not exceed 1,000");
   }
   return new Frequency(Period.ofYears(years));
 }
Exemple #2
0
/**
 * A periodic frequency used by financial products that have a specific event every so often.
 *
 * <p>Frequency is primarily intended to be used to subdivide events within a year.
 *
 * <p>A frequency is allowed to be any non-negative period of days, weeks, month or years. This
 * class provides constants for common frequencies which are best used by static import.
 *
 * <p>A special value, 'Term', is provided for when there are no subdivisions of the entire term.
 * This is also know as 'zero-coupon' or 'once'. It is represented using the period 10,000 years,
 * which allows addition/subtraction to work, producing a date after the end of the term.
 *
 * <p>Each frequency is based on a {@link Period}. The months and years of the period are not
 * normalized, thus it is possible to have a frequency of 12 months and a different one of 1 year.
 * When used, standard date addition rules apply, thus there is no difference between them. Call
 * {@link #normalized()} to apply normalization.
 *
 * <p>The periodic frequency is often expressed as a number of events per year. The {@link
 * #eventsPerYear()} method can be used to obtain this for common frequencies.
 *
 * <h4>Usage</h4>
 *
 * {@code Frequency} implements {@code TemporalAmount} allowing it to be directly added to a date:
 *
 * <pre>
 *  LocalDate later = baseDate.plus(frequency);
 * </pre>
 */
public final class Frequency implements TemporalAmount, Serializable {

  /** Serialization version. */
  private static final long serialVersionUID = 1;
  /** The artificial maximum length of a normal tenor in years. */
  private static final int MAX_YEARS = 1_000;
  /** The artificial maximum length of a normal tenor in months. */
  private static final int MAX_MONTHS = MAX_YEARS * 12;
  /** The artificial length in years of the 'Term' frequency. */
  private static final int TERM_YEARS = 10_000;

  /**
   * A periodic frequency of one day. Also known as daily. There are considered to be 364 events per
   * year with this frequency.
   */
  public static final Frequency P1D = ofDays(1);
  /**
   * A periodic frequency of 1 week (7 days). Also known as weekly. There are considered to be 52
   * events per year with this frequency.
   */
  public static final Frequency P1W = ofWeeks(1);
  /**
   * A periodic frequency of 2 weeks (14 days). Also known as bi-weekly. There are considered to be
   * 26 events per year with this frequency.
   */
  public static final Frequency P2W = ofWeeks(2);
  /**
   * A periodic frequency of 4 weeks (28 days). Also known as lunar. There are considered to be 13
   * events per year with this frequency.
   */
  public static final Frequency P4W = ofWeeks(4);
  /**
   * A periodic frequency of 13 weeks (91 days). There are considered to be 4 events per year with
   * this frequency.
   */
  public static final Frequency P13W = ofWeeks(13);
  /**
   * A periodic frequency of 26 weeks (182 days). There are considered to be 2 events per year with
   * this frequency.
   */
  public static final Frequency P26W = ofWeeks(26);
  /**
   * A periodic frequency of 52 weeks (364 days). There is considered to be 1 event per year with
   * this frequency.
   */
  public static final Frequency P52W = ofWeeks(52);
  /**
   * A periodic frequency of 1 month. Also known as monthly. There are 12 events per year with this
   * frequency.
   */
  public static final Frequency P1M = ofMonths(1);
  /**
   * A periodic frequency of 2 months. Also known as bi-monthly. There are 6 events per year with
   * this frequency.
   */
  public static final Frequency P2M = ofMonths(2);
  /**
   * A periodic frequency of 3 months. Also known as quarterly. There are 4 events per year with
   * this frequency.
   */
  public static final Frequency P3M = ofMonths(3);
  /** A periodic frequency of 4 months. There are 3 events per year with this frequency. */
  public static final Frequency P4M = ofMonths(4);
  /**
   * A periodic frequency of 6 months. Also known as semi-annual. There are 2 events per year with
   * this frequency.
   */
  public static final Frequency P6M = ofMonths(6);
  /**
   * A periodic frequency of 12 months (1 year). Also known as annual. There is 1 event per year
   * with this frequency.
   */
  public static final Frequency P12M = ofMonths(12);
  /**
   * A periodic frequency matching the term. Also known as zero-coupon. This is represented using
   * the period 10,000 years. There are no events per year with this frequency.
   */
  public static final Frequency TERM = new Frequency(Period.ofYears(TERM_YEARS), "Term");

  /** The period of the frequency. */
  private final Period period;
  /** The name of the frequency. */
  private final String name;

  // -------------------------------------------------------------------------
  /**
   * Obtains a periodic frequency from a {@code Period}.
   *
   * <p>The period normally consists of either days and weeks, or months and years. It must also be
   * positive and non-zero.
   *
   * <p>If the number of days is an exact multiple of 7 it will be converted to weeks. Months are
   * not normalized into years.
   *
   * <p>The maximum tenor length is 1,000 years.
   *
   * @param period the period to convert to a periodic frequency
   * @return the periodic frequency
   * @throws IllegalArgumentException if the period is negative, zero or too large
   */
  public static Frequency of(Period period) {
    ArgChecker.notNull(period, "period");
    int days = period.getDays();
    long months = period.toTotalMonths();
    if (months == 0 && days != 0) {
      return ofDays(days);
    }
    if (months > MAX_MONTHS) {
      throw new IllegalArgumentException("Period must not exceed 1000 years");
    }
    return new Frequency(period);
  }

  /**
   * Returns a periodic frequency backed by a period of days.
   *
   * <p>If the number of days is an exact multiple of 7 it will be converted to weeks.
   *
   * @param days the number of days
   * @return the periodic frequency
   * @throws IllegalArgumentException if days is negative or zero
   */
  public static Frequency ofDays(int days) {
    if (days % 7 == 0) {
      return ofWeeks(days / 7);
    }
    return new Frequency(Period.ofDays(days));
  }

  /**
   * Returns a periodic frequency backed by a period of weeks.
   *
   * @param weeks the number of weeks
   * @return the periodic frequency
   * @throws IllegalArgumentException if weeks is negative or zero
   */
  public static Frequency ofWeeks(int weeks) {
    return new Frequency(Period.ofWeeks(weeks), "P" + weeks + "W");
  }

  /**
   * Returns a periodic frequency backed by a period of months.
   *
   * <p>Months are not normalized into years.
   *
   * @param months the number of months
   * @return the periodic frequency
   * @throws IllegalArgumentException if months is negative, zero or over 12,000
   */
  public static Frequency ofMonths(int months) {
    if (months > MAX_MONTHS) {
      throw new IllegalArgumentException("Months must not exceed 12,000");
    }
    return new Frequency(Period.ofMonths(months));
  }

  /**
   * Returns a periodic frequency backed by a period of years.
   *
   * @param years the number of years
   * @return the periodic frequency
   * @throws IllegalArgumentException if years is negative, zero or over 1,000
   */
  public static Frequency ofYears(int years) {
    if (years > MAX_YEARS) {
      throw new IllegalArgumentException("Years must not exceed 1,000");
    }
    return new Frequency(Period.ofYears(years));
  }

  // -------------------------------------------------------------------------
  /**
   * Parses a formatted string representing the frequency.
   *
   * <p>The format can either be based on ISO-8601, such as 'P3M' or without the 'P' prefix e.g.
   * '2W'.
   *
   * <p>The period must be positive and non-zero.
   *
   * @param toParse the string representing the frequency
   * @return the frequency
   * @throws IllegalArgumentException if the frequency cannot be parsed
   */
  @FromString
  public static Frequency parse(String toParse) {
    ArgChecker.notNull(toParse, "toParse");
    if (toParse.equalsIgnoreCase("Term")) {
      return TERM;
    }
    String prefixed = toParse.startsWith("P") ? toParse : "P" + toParse;
    try {
      return Frequency.of(Period.parse(prefixed));
    } catch (DateTimeParseException ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  // -------------------------------------------------------------------------
  /**
   * Creates a periodic frequency.
   *
   * @param period the period to represent
   */
  private Frequency(Period period) {
    this(period, period.toString());
  }

  /**
   * Creates a periodic frequency.
   *
   * @param period the period to represent
   * @param name the name
   */
  private Frequency(Period period, String name) {
    ArgChecker.notNull(period, "period");
    ArgChecker.isFalse(period.isZero(), "Period must not be zero");
    ArgChecker.isFalse(period.isNegative(), "Period must not be negative");
    this.period = period;
    this.name = name;
  }

  // safe deserialization
  private Object readResolve() {
    if (this.equals(TERM)) {
      return TERM;
    }
    return of(period);
  }

  // -------------------------------------------------------------------------
  /**
   * Gets the underlying period of the frequency.
   *
   * @return the period
   */
  public Period getPeriod() {
    return period;
  }

  /**
   * Checks if the periodic frequency is the 'Term' instance.
   *
   * <p>The term instance corresponds to there being no subdivisions of the entire term.
   *
   * @return true if this is the 'Term' instance
   */
  public boolean isTerm() {
    return this == TERM;
  }

  // -------------------------------------------------------------------------
  /**
   * Normalizes the months and years of this tenor.
   *
   * <p>This method returns a tenor of an equivalent length but with any number of months greater
   * than 12 normalized into a combination of months and years.
   *
   * @return the normalized tenor
   */
  public Frequency normalized() {
    Period norm = period.normalized();
    return (norm != period ? Frequency.of(norm) : this);
  }

  // -------------------------------------------------------------------------
  /**
   * Checks if the periodic frequency is week-based.
   *
   * <p>A week-based frequency consists of an integral number of weeks. There must be no day, month
   * or year element.
   *
   * @return true if this is week-based
   */
  public boolean isWeekBased() {
    return period.toTotalMonths() == 0 && period.getDays() % 7 == 0;
  }

  /**
   * Checks if the periodic frequency is month-based.
   *
   * <p>A month-based frequency consists of an integral number of months. Any year-based frequency
   * is also counted as month-based. There must be no day or week element.
   *
   * @return true if this is month-based
   */
  public boolean isMonthBased() {
    return period.toTotalMonths() > 0 && period.getDays() == 0 && isTerm() == false;
  }

  // -------------------------------------------------------------------------
  /**
   * Calculates the number of events that occur in a year.
   *
   * <p>The number of events per year is the number of times that the period occurs per year. Not
   * all periodic frequency instances can be converted to an integer events per year. All constants
   * declared on this class will return a result.
   *
   * <p>Month-based and year-based periodic frequencies are converted by dividing 12 by the number
   * of months. Only the following periodic frequencies return a value - P1M, P2M, P3M, P4M, P6M,
   * P1Y.
   *
   * <p>Day-based and week-based periodic frequencies are converted by dividing 364 by the number of
   * days. Only the following periodic frequencies return a value - P1D, P2D, P4D, P1W, P2W, P4W,
   * P13W, P26W, P52W.
   *
   * <p>The 'Term' periodic frequency returns zero.
   *
   * @return the number of events per year
   * @throws IllegalArgumentException if unable to calculate the number of events per year
   */
  public int eventsPerYear() {
    if (isTerm()) {
      return 0;
    }
    long months = period.toTotalMonths();
    int days = period.getDays();
    if (isMonthBased()) {
      if (12 % months == 0) {
        return (int) (12 / months);
      }
    } else if (months == 0 && 364 % days == 0) {
      return (int) (364 / days);
    }
    throw new IllegalArgumentException("Unable to calculate events per year: " + this);
  }

  // -------------------------------------------------------------------------
  /**
   * Exactly divides this frequency by another.
   *
   * <p>This calculates the integer division of this frequency by the specified frequency. If the
   * result is not an integer, an exception is thrown.
   *
   * <p>Month-based and year-based periodic frequencies are calculated by dividing the total number
   * of months. For example, P6M divided by P3M results in 2, and P2Y divided by P6M returns 4.
   *
   * <p>Day-based and week-based periodic frequencies are calculated by dividing the total number of
   * days. For example, P26W divided by P13W results in 2, and P2W divided by P1D returns 14.
   *
   * <p>The 'Term' frequency throws an exception.
   *
   * @param other the other frequency to divide into this one
   * @return this frequency divided by the other frequency
   * @throws IllegalArgumentException if the frequency does not exactly divide into this one
   */
  public int exactDivide(Frequency other) {
    ArgChecker.notNull(other, "other");
    if (isMonthBased() && other.isMonthBased()) {
      long paymentMonths = getPeriod().toTotalMonths();
      long accrualMonths = other.getPeriod().toTotalMonths();
      if ((paymentMonths % accrualMonths) == 0) {
        return Math.toIntExact(paymentMonths / accrualMonths);
      }
    } else if (period.toTotalMonths() == 0 && other.period.toTotalMonths() == 0) {
      long paymentDays = getPeriod().getDays();
      long accrualDays = other.getPeriod().getDays();
      if ((paymentDays % accrualDays) == 0) {
        return Math.toIntExact(paymentDays / accrualDays);
      }
    }
    throw new IllegalArgumentException(
        Messages.format("Frequency '{}' is not a multiple of '{}'", this, other));
  }

  // -------------------------------------------------------------------------
  /**
   * Gets the value of the specified unit.
   *
   * <p>This will return a value for the years, months and days units. Note that weeks are not
   * included. All other units throw an exception.
   *
   * <p>The 'Term' period is returned as a period of 10,000 years.
   *
   * <p>This method implements {@link TemporalAmount}. It is not intended to be called directly.
   *
   * @param unit the unit to query
   * @return the value of the unit
   * @throws UnsupportedTemporalTypeException if the unit is not supported
   */
  @Override
  public long get(TemporalUnit unit) {
    return period.get(unit);
  }

  /**
   * Gets the unit of this periodic frequency.
   *
   * <p>This returns a list containing years, months and days. Note that weeks are not included.
   *
   * <p>The 'Term' period is returned as a period of 10,000 years.
   *
   * <p>This method implements {@link TemporalAmount}. It is not intended to be called directly.
   *
   * @return a list containing the years, months and days units
   */
  @Override
  public List<TemporalUnit> getUnits() {
    return period.getUnits();
  }

  /**
   * Adds the period of this frequency to the specified date.
   *
   * <p>This method implements {@link TemporalAmount}. It is not intended to be called directly. Use
   * {@link LocalDate#plus(TemporalAmount)} instead.
   *
   * @param temporal the temporal object to add to
   * @return the result with this frequency added
   * @throws DateTimeException if unable to add
   * @throws ArithmeticException if numeric overflow occurs
   */
  @Override
  public Temporal addTo(Temporal temporal) {
    // special case for performance
    if (temporal instanceof LocalDate) {
      LocalDate date = (LocalDate) temporal;
      return date.plusMonths(period.toTotalMonths()).plusDays(period.getDays());
    }
    return period.addTo(temporal);
  }

  /**
   * Subtracts the period of this frequency from the specified date.
   *
   * <p>This method implements {@link TemporalAmount}. It is not intended to be called directly. Use
   * {@link LocalDate#minus(TemporalAmount)} instead.
   *
   * @param temporal the temporal object to subtract from
   * @return the result with this frequency subtracted
   * @throws DateTimeException if unable to subtract
   * @throws ArithmeticException if numeric overflow occurs
   */
  @Override
  public Temporal subtractFrom(Temporal temporal) {
    // special case for performance
    if (temporal instanceof LocalDate) {
      LocalDate date = (LocalDate) temporal;
      return date.minusMonths(period.toTotalMonths()).minusDays(period.getDays());
    }
    return period.subtractFrom(temporal);
  }

  // -------------------------------------------------------------------------
  /**
   * Checks if this periodic frequency equals another periodic frequency.
   *
   * <p>The comparison checks the frequency period.
   *
   * @param obj the other frequency, null returns false
   * @return true if equal
   */
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
      return false;
    }
    Frequency other = (Frequency) obj;
    return period.equals(other.period);
  }

  /**
   * Returns a suitable hash code for the periodic frequency.
   *
   * @return the hash code
   */
  @Override
  public int hashCode() {
    return period.hashCode();
  }

  /**
   * Returns a formatted string representing the periodic frequency.
   *
   * <p>The format is a combination of the quantity and unit, such as P1D, P2W, P3M, P4Y. The 'Term'
   * amount is returned as 'Term'.
   *
   * @return the formatted frequency
   */
  @ToString
  @Override
  public String toString() {
    return name;
  }
}
  @Test
  public void consistencyTest() {
    final double tol = 1.e-12;

    final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1);
    final double timeToExpiry = ACT365F.yearFraction(TRADE_DATE, optionExpiry);
    final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5));
    final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry);

    final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652};
    final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS);

    final IntrinsicIndexDataBundle adjCurves =
        PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
    final double fwdSpread =
        INDEX_CAL.defaultAdjustedForwardSpread(
            fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves);
    final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves);
    final BlackIndexOptionPricer pricerWithFwd =
        new BlackIndexOptionPricer(
            fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
    final BlackIndexOptionPricer pricerNoFwd =
        new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurves);

    final boolean[] payer = new boolean[] {true, false};
    final double vol = 0.4;
    final double strikeSpread = 0.015;
    final ISDACompliantYieldCurve fwdYC = YIELD_CURVE.withOffset(timeToExpiry);
    final double strikePrice =
        CONVERTER.quotedSpreadToPUF(fwdCDX, INDEX_COUPON, fwdYC, strikeSpread);
    final IndexOptionStrike exPriceAmount = new ExerciseAmount(strikePrice);
    final IndexOptionStrike exSpreadAmount = new SpreadBasedStrike(strikeSpread);

    for (int i = 0; i < 2; ++i) {
      /** Consistency between option pricing methods */
      final double premFromPrice = pricerWithFwd.getOptionPremium(exPriceAmount, vol, payer[i]);
      final double premFromSpread = pricerWithFwd.getOptionPremium(exSpreadAmount, vol, payer[i]);
      final double premFromPriceBare =
          pricerWithFwd.getOptionPriceForPriceQuotedIndex(strikePrice, vol, payer[i]);
      final double premFromSpreadBare =
          pricerWithFwd.getOptionPriceForSpreadQuotedIndex(strikeSpread, vol, payer[i]);
      assertEquals(premFromPrice, premFromSpread, tol);
      assertEquals(premFromSpread, premFromPriceBare, tol);
      assertEquals(premFromPriceBare, premFromSpreadBare, tol);

      /** Consistency between constructors */
      final double PremFromPriceNoFwd = pricerNoFwd.getOptionPremium(exPriceAmount, vol, payer[i]);
      assertEquals(premFromPrice, PremFromPriceNoFwd, tol);

      /** Consistency with implied vol */
      final double vol1 =
          pricerWithFwd.getImpliedVolatility(exPriceAmount, premFromPrice, payer[i]);
      final double vol2 =
          pricerWithFwd.getImpliedVolatility(exSpreadAmount, premFromSpread, payer[i]);
      final double vol3 =
          pricerWithFwd.getImpliedVolForExercisePrice(strikePrice, premFromPriceBare, payer[i]);
      final double vol4 =
          pricerWithFwd.getImpliedVolForSpreadStrike(strikeSpread, premFromSpreadBare, payer[i]);
      assertEquals(vol, vol1, tol);
      assertEquals(vol, vol2, tol);
      assertEquals(vol, vol3, tol);
      assertEquals(vol, vol4, tol);
    }
  }
/**
 * Test class for the replication method for CMS caplet/floorlet using a SABR smile with
 * extrapolation.
 */
@Test
public class CapFloorCMSSABRExtrapolationRightReplicationMethodTest {

  private static final MulticurveProviderDiscount MULTICURVES =
      MulticurveProviderDiscountDataSets.createMulticurveEurUsd();
  private static final IborIndex EURIBOR6M =
      MulticurveProviderDiscountDataSets.getIndexesIborMulticurveEurUsd()[1];
  private static final Currency EUR = EURIBOR6M.getCurrency();
  private static final HolidayCalendar CALENDAR =
      MulticurveProviderDiscountDataSets.getEURCalendar();

  private static final SABRInterestRateParameters SABR_PARAMETER = SABRDataSets.createSABR1();
  private static final GeneratorSwapFixedIbor EUR1YEURIBOR6M =
      GeneratorSwapFixedIborMaster.getInstance().getGenerator("EUR1YEURIBOR6M", CALENDAR);
  private static final SABRSwaptionProviderDiscount SABR_MULTICURVES =
      new SABRSwaptionProviderDiscount(MULTICURVES, SABR_PARAMETER, EUR1YEURIBOR6M);

  // Swap 5Y
  private static final BusinessDayConvention BUSINESS_DAY =
      BusinessDayConventions.MODIFIED_FOLLOWING;
  private static final boolean IS_EOM = true;
  private static final Period ANNUITY_TENOR = Period.ofYears(5);
  private static final ZonedDateTime SETTLEMENT_DATE = DateUtils.getUTCDate(2020, 4, 28);
  // Fixed leg: Semi-annual bond
  private static final Period FIXED_PAYMENT_PERIOD = Period.ofMonths(6);
  private static final DayCount FIXED_DAY_COUNT = DayCounts.THIRTY_U_360;
  private static final double RATE = 0.0325;
  private static final boolean FIXED_IS_PAYER = true;
  private static final AnnuityCouponFixedDefinition FIXED_ANNUITY =
      AnnuityCouponFixedDefinition.from(
          EUR,
          SETTLEMENT_DATE,
          ANNUITY_TENOR,
          FIXED_PAYMENT_PERIOD,
          CALENDAR,
          FIXED_DAY_COUNT,
          BUSINESS_DAY,
          IS_EOM,
          1.0,
          RATE,
          FIXED_IS_PAYER);
  // Ibor leg: quarterly money
  private static final AnnuityCouponIborDefinition IBOR_ANNUITY =
      AnnuityCouponIborDefinition.from(
          SETTLEMENT_DATE, ANNUITY_TENOR, 1.0, EURIBOR6M, !FIXED_IS_PAYER, CALENDAR);
  // CMS coupon construction
  private static final IndexSwap CMS_INDEX =
      new IndexSwap(FIXED_PAYMENT_PERIOD, FIXED_DAY_COUNT, EURIBOR6M, ANNUITY_TENOR, CALENDAR);
  private static final SwapFixedIborDefinition SWAP_DEFINITION =
      new SwapFixedIborDefinition(FIXED_ANNUITY, IBOR_ANNUITY);
  private static final ZonedDateTime FIXING_DATE =
      ScheduleCalculator.getAdjustedDate(SETTLEMENT_DATE, -EURIBOR6M.getSpotLag(), CALENDAR);
  private static final ZonedDateTime ACCRUAL_START_DATE = SETTLEMENT_DATE; // pre-fixed
  private static final ZonedDateTime ACCRUAL_END_DATE =
      ScheduleCalculator.getAdjustedDate(
          ACCRUAL_START_DATE, FIXED_PAYMENT_PERIOD, BUSINESS_DAY, CALENDAR);
  private static final ZonedDateTime PAYMENT_DATE = ACCRUAL_END_DATE;
  private static final DayCount PAYMENT_DAY_COUNT = DayCounts.ACT_360;
  private static final double ACCRUAL_FACTOR =
      DayCountUtils.yearFraction(PAYMENT_DAY_COUNT, ACCRUAL_START_DATE, ACCRUAL_END_DATE);
  private static final double NOTIONAL = 10000000; // 10m
  private static final CouponCMSDefinition CMS_COUPON_RECEIVER_DEFINITION =
      CouponCMSDefinition.from(
          PAYMENT_DATE,
          ACCRUAL_START_DATE,
          ACCRUAL_END_DATE,
          ACCRUAL_FACTOR,
          NOTIONAL,
          FIXING_DATE,
          SWAP_DEFINITION,
          CMS_INDEX);
  private static final CouponCMSDefinition CMS_COUPON_PAYER_DEFINITION =
      CouponCMSDefinition.from(
          PAYMENT_DATE,
          ACCRUAL_START_DATE,
          ACCRUAL_END_DATE,
          ACCRUAL_FACTOR,
          -NOTIONAL,
          FIXING_DATE,
          SWAP_DEFINITION,
          CMS_INDEX);
  // Cap/Floor construction
  private static final double STRIKE = 0.04;
  private static final boolean IS_CAP = true;
  private static final CapFloorCMSDefinition CMS_CAP_LONG_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, STRIKE, IS_CAP);
  private static final CapFloorCMSDefinition CMS_CAP_SHORT_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_PAYER_DEFINITION, STRIKE, IS_CAP);
  private static final CapFloorCMSDefinition CMS_CAP_0_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, 0.0, IS_CAP);
  // to derivatives
  private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2010, 8, 18);

  private static final CouponCMS CMS_COUPON =
      (CouponCMS) CMS_COUPON_RECEIVER_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_0 =
      (CapFloorCMS) CMS_CAP_0_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_LONG =
      (CapFloorCMS) CMS_CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_SHORT =
      (CapFloorCMS) CMS_CAP_SHORT_DEFINITION.toDerivative(REFERENCE_DATE);
  // Calculators & methods
  private static final CapFloorCMSSABRReplicationMethod METHOD_STANDARD_CAP =
      CapFloorCMSSABRReplicationMethod.getDefaultInstance();
  private static final CouponCMSSABRReplicationMethod METHOD_STANDARD_CPN =
      CouponCMSSABRReplicationMethod.getInstance();
  private static final CouponCMSDiscountingMethod METHOD_DSC_CPN =
      CouponCMSDiscountingMethod.getInstance();

  private static final double CUT_OFF_STRIKE = 0.10;
  private static final double MU = 2.50;
  private static final CapFloorCMSSABRExtrapolationRightReplicationMethod METHOD_EXTRAPOLATION_CAP =
      new CapFloorCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, MU);
  private static final CouponCMSSABRExtrapolationRightReplicationMethod METHOD_EXTRAPOLATION_CPN =
      new CouponCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, MU);
  // Calculators
  private static final PresentValueSABRSwaptionRightExtrapolationCalculator PVSSXC =
      new PresentValueSABRSwaptionRightExtrapolationCalculator(CUT_OFF_STRIKE, MU);
  private static final PresentValueCurveSensitivitySABRSwaptionRightExtrapolationCalculator
      PVCSSSXC =
          new PresentValueCurveSensitivitySABRSwaptionRightExtrapolationCalculator(
              CUT_OFF_STRIKE, MU);
  private static final PresentValueSABRSensitivitySABRSwaptionRightExtrapolationCalculator
      PVSSSSXC =
          new PresentValueSABRSensitivitySABRSwaptionRightExtrapolationCalculator(
              CUT_OFF_STRIKE, MU);

  private static final double SHIFT = 1.0E-6;
  private static final ParameterSensitivityParameterCalculator<SABRSwaptionProviderInterface>
      PS_SS_C = new ParameterSensitivityParameterCalculator<>(PVCSSSXC);
  private static final ParameterSensitivitySABRSwaptionDiscountInterpolatedFDCalculator PS_SS_FDC =
      new ParameterSensitivitySABRSwaptionDiscountInterpolatedFDCalculator(PVSSXC, SHIFT);

  private static final double TOLERANCE_PV = 1.0E-2;
  private static final double TOLERANCE_PV_DELTA = 5.0E+3; // 0.01 currency unit for 1 bp.

  /**
   * Test the present value for a CMS coupon with pricing by replication in the SABR with
   * extrapolation framework. The present value is tested against hard-coded value and cap of strike
   * 0.
   */
  public void presentValue() {
    // CMS cap/floor with strike 0 has the same price as a CMS coupon.
    final double priceCouponStd =
        METHOD_STANDARD_CPN.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponStd =
        priceCouponStd
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double priceCouponExtra =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double rateCouponExtra =
        priceCouponExtra
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double priceCouponNoAdj =
        METHOD_DSC_CPN.presentValue(CMS_COUPON, MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponNoAdj =
        priceCouponNoAdj
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    assertEquals(
        "Extrapolation: comparison with standard method", rateCouponStd > rateCouponExtra, true);
    assertEquals(
        "Extrapolation: comparison with no convexity adjustment",
        rateCouponExtra > rateCouponNoAdj,
        true);
    final double rateCouponExtraExpected = 0.0189864; // From previous run.
    assertEquals("Extrapolation: hard-coded value", rateCouponExtraExpected, rateCouponExtra, 1E-6);
    final double priceCap0Extra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_0, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    assertEquals(
        "Extrapolation: CMS coupon vs Cap 0", priceCouponExtra, priceCap0Extra, TOLERANCE_PV);
  }

  /**
   * Tests the price of CMS coupon and cap/floor using replication in the SABR framework. Method v
   * Calculator.
   */
  public void presentValueMethodVsCalculator() {
    final double pvMethod =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double pvCalculator =
        CMS_CAP_LONG.accept(PVSSXC, SABR_MULTICURVES).getAmount(EUR).getAmount();
    assertEquals(
        "CMS cap/floor SABR: Present value : method vs calculator",
        pvMethod,
        pvCalculator,
        TOLERANCE_PV);
  }

  /**
   * Test the present value for a CMS cap with pricing by replication in the SABR with extrapolation
   * framework. The present value is tested against hard-coded value and a long/short parity is
   * tested.
   */
  public void presentValueReplicationCap() {
    // CMS cap/floor with strike 0 has the same price as a CMS coupon.
    final double priceCapLongStd =
        METHOD_STANDARD_CAP.presentValue(CMS_CAP_LONG, SABR_MULTICURVES).getAmount(EUR).getAmount();
    final double priceCapLongExtra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double priceCapShortExtra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_SHORT, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    assertEquals(
        "CMS cap by replication - Extrapolation: comparison with standard method",
        priceCapLongStd > priceCapLongExtra,
        true);
    final double priceCapExtraExpected = 30696.572; // From previous run.
    assertEquals(
        "CMS cap by replication - Extrapolation: hard-coded value",
        priceCapExtraExpected,
        priceCapLongExtra,
        TOLERANCE_PV);
    assertEquals(
        "CMS cap by replication - Extrapolation: long/short parity",
        -priceCapShortExtra,
        priceCapLongExtra,
        TOLERANCE_PV);
  }

  /**
   * Test the present value rate sensitivity for a CMS cap with pricing by replication in the SABR
   * with extrapolation framework.
   */
  public void presentValueCurveSensitivity() {
    final MultipleCurrencyParameterSensitivity pvpsCapLongExact =
        PS_SS_C.calculateSensitivity(
            CMS_CAP_LONG, SABR_MULTICURVES, SABR_MULTICURVES.getMulticurveProvider().getAllNames());
    final MultipleCurrencyParameterSensitivity pvpsCapLongFD =
        PS_SS_FDC.calculateSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "SwaptionPhysicalFixedIborSABRMethod: presentValueCurveSensitivity ",
        pvpsCapLongExact,
        pvpsCapLongFD,
        TOLERANCE_PV_DELTA);
    final MultipleCurrencyParameterSensitivity pvpsCapShortExact =
        PS_SS_C.calculateSensitivity(
            CMS_CAP_SHORT,
            SABR_MULTICURVES,
            SABR_MULTICURVES.getMulticurveProvider().getAllNames());
    final MultipleCurrencyParameterSensitivity pvpsCapShortFD =
        PS_SS_FDC.calculateSensitivity(CMS_CAP_SHORT, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "SwaptionPhysicalFixedIborSABRMethod: presentValueCurveSensitivity ",
        pvpsCapShortExact,
        pvpsCapShortFD,
        TOLERANCE_PV_DELTA);
  }

  /**
   * Test the present value rate sensitivity for a CMS cap with pricing by replication in the SABR
   * with extrapolation framework. Method v Calculator.
   */
  public void presentValueCurveSensitivityMethodVsCalculator() {
    final MultipleCurrencyMulticurveSensitivity pvcsMethod =
        METHOD_EXTRAPOLATION_CAP.presentValueCurveSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    final MultipleCurrencyMulticurveSensitivity pvcsCalculator =
        CMS_CAP_LONG.accept(PVCSSSXC, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "CMS cap/floor SABR: Present value : method vs calculator",
        pvcsMethod,
        pvcsCalculator,
        TOLERANCE_PV_DELTA);
  }

  /** Tests the cap present value SABR parameters sensitivity vs finite difference. */
  public void presentValueSABRSensitivity() {
    final double pv =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final PresentValueSABRSensitivityDataBundle pvsCapLong =
        METHOD_EXTRAPOLATION_CAP.presentValueSABRSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final double maturity =
        CMS_CAP_LONG
                .getUnderlyingSwap()
                .getFixedLeg()
                .getNthPayment(
                    CMS_CAP_LONG.getUnderlyingSwap().getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - CMS_CAP_LONG.getSettlementTime();
    final DoublesPair expectedExpiryTenor = DoublesPair.of(CMS_CAP_LONG.getFixingTime(), maturity);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        SABRDataSets.createSABR1AlphaBumped(shiftAlpha);
    final SABRSwaptionProviderDiscount sabrBundleAlphaBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterAlphaBumped, EUR1YEURIBOR6M);
    final double pvLongPayerAlphaBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleAlphaBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedAlphaSensi = (pvLongPayerAlphaBumped - pv) / shiftAlpha;
    assertEquals("Number of alpha sensitivity", pvsCapLong.getAlpha().getMap().keySet().size(), 1);
    assertEquals(
        "Alpha sensitivity expiry/tenor",
        pvsCapLong.getAlpha().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Alpha sensitivity value",
        expectedAlphaSensi,
        pvsCapLong.getAlpha().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped = SABRDataSets.createSABR1RhoBumped();
    final SABRSwaptionProviderDiscount sabrBundleRhoBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterRhoBumped, EUR1YEURIBOR6M);
    final double pvLongPayerRhoBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleRhoBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedRhoSensi = (pvLongPayerRhoBumped - pv) / shift;
    assertEquals("Number of rho sensitivity", pvsCapLong.getRho().getMap().keySet().size(), 1);
    assertEquals(
        "Rho sensitivity expiry/tenor",
        pvsCapLong.getRho().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Rho sensitivity value",
        expectedRhoSensi,
        pvsCapLong.getRho().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped = SABRDataSets.createSABR1NuBumped();
    final SABRSwaptionProviderDiscount sabrBundleNuBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterNuBumped, EUR1YEURIBOR6M);
    final double pvLongPayerNuBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleNuBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedNuSensi = (pvLongPayerNuBumped - pv) / shift;
    assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1);
    assertTrue(
        "Nu sensitivity expiry/tenor",
        pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor));
    assertEquals(
        "Nu sensitivity value",
        expectedNuSensi,
        pvsCapLong.getNu().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
  }

  /** Tests the coupon present value SABR parameters sensitivity vs finite difference. */
  public void presentValueSABRSensitivityCoupon() {
    final double pv =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final PresentValueSABRSensitivityDataBundle pvsCpn =
        METHOD_EXTRAPOLATION_CPN.presentValueSABRSensitivity(CMS_COUPON, SABR_MULTICURVES);
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final double maturity =
        CMS_COUPON
                .getUnderlyingSwap()
                .getFixedLeg()
                .getNthPayment(
                    CMS_COUPON.getUnderlyingSwap().getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - CMS_COUPON.getSettlementTime();
    final DoublesPair expectedExpiryTenor = DoublesPair.of(CMS_COUPON.getFixingTime(), maturity);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        SABRDataSets.createSABR1AlphaBumped(shiftAlpha);
    final SABRSwaptionProviderDiscount sabrBundleAlphaBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterAlphaBumped, EUR1YEURIBOR6M);
    final double pvLongPayerAlphaBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleAlphaBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedAlphaSensi = (pvLongPayerAlphaBumped - pv) / shiftAlpha;
    assertEquals("Number of alpha sensitivity", pvsCpn.getAlpha().getMap().keySet().size(), 1);
    assertEquals(
        "Alpha sensitivity expiry/tenor",
        pvsCpn.getAlpha().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Alpha sensitivity value",
        expectedAlphaSensi,
        pvsCpn.getAlpha().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped = SABRDataSets.createSABR1RhoBumped();
    final SABRSwaptionProviderDiscount sabrBundleRhoBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterRhoBumped, EUR1YEURIBOR6M);
    final double pvLongPayerRhoBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleRhoBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedRhoSensi = (pvLongPayerRhoBumped - pv) / shift;
    assertEquals("Number of rho sensitivity", pvsCpn.getRho().getMap().keySet().size(), 1);
    assertEquals(
        "Rho sensitivity expiry/tenor",
        pvsCpn.getRho().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Rho sensitivity value",
        expectedRhoSensi,
        pvsCpn.getRho().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Nu sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped = SABRDataSets.createSABR1NuBumped();
    final SABRSwaptionProviderDiscount sabrBundleNuBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterNuBumped, EUR1YEURIBOR6M);
    final double pvLongPayerNuBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleNuBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedNuSensi = (pvLongPayerNuBumped - pv) / shift;
    assertEquals("Number of nu sensitivity", pvsCpn.getNu().getMap().keySet().size(), 1);
    assertTrue(
        "Nu sensitivity expiry/tenor",
        pvsCpn.getNu().getMap().keySet().contains(expectedExpiryTenor));
    assertEquals(
        "Nu sensitivity value",
        expectedNuSensi,
        pvsCpn.getNu().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
  }

  /** Tests the present value SABR parameters sensitivity: Method vs Calculator. */
  public void presentValueSABRSensitivityMethodVsCalculator() {
    final PresentValueSABRSensitivityDataBundle pvssMethod =
        METHOD_EXTRAPOLATION_CAP.presentValueSABRSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    final PresentValueSABRSensitivityDataBundle pvssCalculator =
        CMS_CAP_LONG.accept(PVSSSSXC, SABR_MULTICURVES);
    assertEquals(
        "CMS cap/floor SABR: Present value SABR sensitivity: method vs calculator",
        pvssMethod,
        pvssCalculator);
  }

  /** Tests the present value strike sensitivity: Cap. */
  public void presentValueStrikeSensitivityCap() {
    final double[] strikes = new double[] {0.0001, 0.0010, 0.0050, 0.0100, 0.0200, 0.0400, 0.0500};
    final int nbStrikes = strikes.length;
    final double shift = 1.0E-5;
    final double[] errorRelative = new double[nbStrikes];
    for (int loopstrike = 0; loopstrike < nbStrikes; loopstrike++) {
      final CapFloorCMSDefinition cmsCapDefinition =
          CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike], IS_CAP);
      final CapFloorCMSDefinition cmsCapShiftUpDefinition =
          CapFloorCMSDefinition.from(
              CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike] + shift, IS_CAP);
      final CapFloorCMSDefinition cmsCapShiftDoDefinition =
          CapFloorCMSDefinition.from(
              CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike] - shift, IS_CAP);
      final CapFloorCMS cmsCap = (CapFloorCMS) cmsCapDefinition.toDerivative(REFERENCE_DATE);
      final CapFloorCMS cmsCapShiftUp =
          (CapFloorCMS) cmsCapShiftUpDefinition.toDerivative(REFERENCE_DATE);
      final CapFloorCMS cmsCapShiftDo =
          (CapFloorCMS) cmsCapShiftDoDefinition.toDerivative(REFERENCE_DATE);
      final double pvShiftUp =
          METHOD_EXTRAPOLATION_CAP
              .presentValue(cmsCapShiftUp, SABR_MULTICURVES)
              .getAmount(EUR)
              .getAmount();
      final double pvShiftDo =
          METHOD_EXTRAPOLATION_CAP
              .presentValue(cmsCapShiftDo, SABR_MULTICURVES)
              .getAmount(EUR)
              .getAmount();
      final double sensiExpected = (pvShiftUp - pvShiftDo) / (2 * shift);
      final double sensiComputed =
          METHOD_EXTRAPOLATION_CAP.presentValueStrikeSensitivity(cmsCap, SABR_MULTICURVES);
      errorRelative[loopstrike] = (sensiExpected - sensiComputed) / sensiExpected;
      assertEquals(
          "CMS cap/floor SABR: Present value strike sensitivity " + loopstrike,
          0,
          errorRelative[loopstrike],
          5.0E-3);
    }
  }

  /**
   * Tests to estimate the impact of mu on the CMS coupon pricing. "enabled = false" for the
   * standard testing.
   */
  public void testPriceMultiMu() {
    final double[] mu = new double[] {1.10, 1.30, 1.55, 2.25, 3.50, 6.00, 15.0};
    final int nbMu = mu.length;
    final double priceCouponStd =
        METHOD_STANDARD_CPN.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
    @SuppressWarnings("unused")
    final double rateCouponStd =
        priceCouponStd
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double[] priceCouponExtra = new double[nbMu];
    final double[] rateCouponExtra = new double[nbMu];
    for (int loopmu = 0; loopmu < nbMu; loopmu++) {
      final CouponCMSSABRExtrapolationRightReplicationMethod methodExtrapolation =
          new CouponCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, mu[loopmu]);
      priceCouponExtra[loopmu] =
          methodExtrapolation.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
      rateCouponExtra[loopmu] =
          priceCouponExtra[loopmu]
              / (CMS_COUPON.getPaymentYearFraction()
                  * CMS_COUPON.getNotional()
                  * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    }
    final double priceCouponNoAdj =
        METHOD_DSC_CPN.presentValue(CMS_COUPON, MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponNoAdj =
        priceCouponNoAdj
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    for (int loopmu = 1; loopmu < nbMu; loopmu++) {
      assertTrue(
          "Extrapolation: comparison with standard method",
          rateCouponExtra[loopmu - 1] > rateCouponExtra[loopmu]);
    }
    assertTrue(
        "Extrapolation: comparison with standard method",
        rateCouponExtra[nbMu - 1] > rateCouponNoAdj);
  }
}
  @SuppressWarnings("unused")
  @Test
  public void limitTest() {
    final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1);
    final double timeToExpiry = ACT365F.yearFraction(TRADE_DATE, optionExpiry);
    final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5));
    final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry);

    final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652};
    final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS);

    final IntrinsicIndexDataBundle adjCurves =
        PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
    final double fwdSpread =
        INDEX_CAL.defaultAdjustedForwardSpread(
            fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves);
    final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves);

    final BlackIndexOptionPricer pricerWithFwd =
        new BlackIndexOptionPricer(
            fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
    final double modStrikeLimit = INDEX_COUPON + fwdCDX.getLGD() / fwdAnnuity;
    final double vol = 0.4;
    final double payLargeSpLimit =
        fwdAnnuity
            * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, true);
    final double recLargeSpLimit =
        fwdAnnuity
            * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, false);

    final double largeStrikeSp = 75.0;
    final double payLargeStrikeSp =
        pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, true);
    final double recLargeStrikeSp =
        pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, false);
    assertEquals(payLargeSpLimit, payLargeStrikeSp, 1.e-12);
    assertEquals(
        recLargeSpLimit,
        recLargeStrikeSp,
        1.e-3); // larger strike spread ends up with failure in root-finding

    /** Exception expected */
    final double negativeTime = -0.5;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, negativeTime, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("timeToExpiry must be positive. Value given " + negativeTime, e.getMessage());
    }
    try {
      new BlackIndexOptionPricer(
          fwdStartingCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("fwdCDS should be a Forward CDS", e.getMessage());
    }
    final double negativeCoupon = -150.0 * 1.0e-4;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, negativeCoupon, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("indexCoupon must be positive", e.getMessage());
    }
    final double negativeFwdSp = -0.5;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, negativeFwdSp, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("defaultAdjustedFwdSpread must be positive", e.getMessage());
    }
    final double negativeAnn = -0.3;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, negativeAnn);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("pvFwdAnnuity must be positive", e.getMessage());
    }
    final double largeAnn = fwdCDX.getProtectionEnd() * 2.0;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, largeAnn);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals(
          "Value of annuity of "
              + largeAnn
              + " is greater than length (in years) of forward CDS. Annuity should be given for unit notional",
          e.getMessage());
    }

    final double smallStrike = -0.9;
    try {
      pricerWithFwd.getOptionPriceForPriceQuotedIndex(smallStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
    final double largeStrike = 0.9;
    try {
      pricerWithFwd.getOptionPriceForPriceQuotedIndex(largeStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
    try {
      pricerWithFwd.getOptionPriceForSpreadQuotedIndex(smallStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
  }
/**
 * Build of curve in several blocks with relevant Jacobian matrices. EUR: discounting/ON forward;
 * USD: discounting/ON forward. Standard test data set: 2014-03-10
 */
public class StandardDataSetsEURUSDForex {

  private static final ZonedDateTime NOW = DateUtils.getUTCDate(2014, 3, 10);

  private static final Interpolator1D INTERPOLATOR_LINEAR =
      CombinedInterpolatorExtrapolatorFactory.getInterpolator(
          Interpolator1DFactory.LINEAR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR);

  private static final LastTimeCalculator MATURITY_CALCULATOR = LastTimeCalculator.getInstance();
  private static final double TOLERANCE_ROOT = 1.0E-10;
  private static final int STEP_MAX = 100;

  private static final HolidayCalendar TARGET = HolidayCalendars.EUTA;
  private static final HolidayCalendar NYC = HolidayCalendars.SAT_SUN;
  private static final Currency EUR = Currency.EUR;
  private static final Currency USD = Currency.USD;
  private static final double FX_EURUSD = 1.38775;
  private static final FxMatrix FX_MATRIX = FxMatrix.builder().addRate(EUR, USD, FX_EURUSD).build();

  private static final double NOTIONAL = 1.0;

  private static final GeneratorSwapFixedON GENERATOR_OIS_EUR =
      GeneratorSwapFixedONMaster.getInstance().getGenerator("EUR1YEONIA", TARGET);
  private static final IndexON EUREONIA = GENERATOR_OIS_EUR.getIndex();
  private static final GeneratorSwapFixedON GENERATOR_OIS_USD =
      GeneratorSwapFixedONMaster.getInstance().getGenerator("USD1YFEDFUND", NYC);
  private static final IndexON USDFEDFUND = GENERATOR_OIS_USD.getIndex();
  private static final IndexON INDEX_ON_EUR = GENERATOR_OIS_EUR.getIndex();
  private static final IndexON INDEX_ON_USD = GENERATOR_OIS_USD.getIndex();
  private static final GeneratorDepositON GENERATOR_DEPOSIT_ON_EUR =
      new GeneratorDepositON("EUR Deposit ON", EUR, TARGET, INDEX_ON_EUR.getDayCount());
  private static final GeneratorDepositON GENERATOR_DEPOSIT_ON_USD =
      new GeneratorDepositON("USD Deposit ON", USD, TARGET, INDEX_ON_USD.getDayCount());
  private static final GeneratorForexSwap GENERATOR_FX_EURUSD =
      new GeneratorForexSwap(
          "EURUSD", EUR, USD, TARGET, 2, GENERATOR_OIS_EUR.getBusinessDayConvention(), true);

  private static final ZonedDateTimeDoubleTimeSeries TS_EMPTY =
      ImmutableZonedDateTimeDoubleTimeSeries.ofEmptyUTC();
  private static final ZonedDateTimeDoubleTimeSeries TS_ON_USD_WITH_TODAY =
      ImmutableZonedDateTimeDoubleTimeSeries.ofUTC(
          new ZonedDateTime[] {
            DateUtils.getUTCDate(2011, 9, 27), DateUtils.getUTCDate(2011, 9, 28)
          },
          new double[] {0.07, 0.08});
  private static final ZonedDateTimeDoubleTimeSeries TS_ON_USD_WITHOUT_TODAY =
      ImmutableZonedDateTimeDoubleTimeSeries.ofUTC(
          new ZonedDateTime[] {
            DateUtils.getUTCDate(2011, 9, 27), DateUtils.getUTCDate(2011, 9, 28)
          },
          new double[] {0.07, 0.08});
  private static final ZonedDateTimeDoubleTimeSeries[] TS_FIXED_OIS_USD_WITH_TODAY =
      new ZonedDateTimeDoubleTimeSeries[] {TS_EMPTY, TS_ON_USD_WITH_TODAY};
  private static final ZonedDateTimeDoubleTimeSeries[] TS_FIXED_OIS_USD_WITHOUT_TODAY =
      new ZonedDateTimeDoubleTimeSeries[] {TS_EMPTY, TS_ON_USD_WITHOUT_TODAY};

  private static final String CURVE_NAME_DSC_EUR = "EUR Dsc";
  private static final String CURVE_NAME_DSC_USD = "USD Dsc";

  /** Market values for the dsc USD curve */
  private static final double[] DSC_USD_MARKET_QUOTES =
      new double[] {
        0.0015, 0.0015, 7.9E-4, 7.8E-4, 8.3E-4, 0.0009, 0.0010, 0.00112, 0.0030525, 0.00686, 0.0109,
        0.01465, 0.01782, 0.02048, 0.02264, 0.02445, 0.02597
      };
  /** Generators for the dsc USD curve */
  private static final GeneratorInstrument<? extends GeneratorAttribute>[] DSC_USD_GENERATORS =
      new GeneratorInstrument<?>[] {
        GENERATOR_DEPOSIT_ON_USD,
        GENERATOR_DEPOSIT_ON_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD,
        GENERATOR_OIS_USD
      };
  /** Tenors for the dsc USD curve */
  private static final Period[] DSC_USD_TENOR =
      new Period[] {
        Period.ofDays(0),
        Period.ofDays(1),
        Period.ofMonths(1),
        Period.ofMonths(2),
        Period.ofMonths(3),
        Period.ofMonths(6),
        Period.ofMonths(9),
        Period.ofYears(1),
        Period.ofYears(2),
        Period.ofYears(3),
        Period.ofYears(4),
        Period.ofYears(5),
        Period.ofYears(6),
        Period.ofYears(7),
        Period.ofYears(8),
        Period.ofYears(9),
        Period.ofYears(10)
      };

  private static final GeneratorAttributeIR[] DSC_USD_ATTR =
      new GeneratorAttributeIR[DSC_USD_TENOR.length];

  static {
    for (int loopins = 0; loopins < 2; loopins++) {
      DSC_USD_ATTR[loopins] = new GeneratorAttributeIR(DSC_USD_TENOR[loopins], Period.ZERO);
    }
    for (int loopins = 2; loopins < DSC_USD_TENOR.length; loopins++) {
      DSC_USD_ATTR[loopins] = new GeneratorAttributeIR(DSC_USD_TENOR[loopins]);
    }
  }

  /** Market values for the dsc EUR curve - calibrated on OIS */
  private static final double[] DSC_EUR_MARKET_QUOTES =
      new double[] {
        0.001725, 0.00170, 0.00196, 0.00193, 0.00186, 0.00181, 0.00172, 0.00174, 0.002015, 0.00321,
        0.00491, 0.0068, 0.01061, 0.01539
      };
  /** Generators for the dsc EUR curve */
  private static final GeneratorInstrument<? extends GeneratorAttribute>[] DSC_EUR_GENERATORS =
      new GeneratorInstrument<?>[] {
        GENERATOR_DEPOSIT_ON_EUR,
        GENERATOR_DEPOSIT_ON_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR,
        GENERATOR_OIS_EUR
      };
  /** Tenors for the dsc EUR curve */
  private static final Period[] DSC_EUR_TENOR =
      new Period[] {
        Period.ofDays(0),
        Period.ofDays(1),
        Period.ofMonths(1),
        Period.ofMonths(2),
        Period.ofMonths(3),
        Period.ofMonths(6),
        Period.ofMonths(9),
        Period.ofYears(1),
        Period.ofYears(2),
        Period.ofYears(3),
        Period.ofYears(4),
        Period.ofYears(5),
        Period.ofYears(7),
        Period.ofYears(10)
      };

  private static final GeneratorAttributeIR[] DSC_EUR_ATTR =
      new GeneratorAttributeIR[DSC_EUR_TENOR.length];

  static {
    for (int loopins = 0; loopins < 2; loopins++) {
      DSC_EUR_ATTR[loopins] = new GeneratorAttributeIR(DSC_EUR_TENOR[loopins], Period.ZERO);
    }
    for (int loopins = 2; loopins < DSC_EUR_TENOR.length; loopins++) {
      DSC_EUR_ATTR[loopins] = new GeneratorAttributeIR(DSC_EUR_TENOR[loopins]);
    }
  }

  /** Market values for the FX EUR USD FX swaps */
  private static final double[] DSC_EURUSD_MARKET_FORWARD =
      new double[] {
        1.387673, 1.387625, 1.3875895, 1.38755, 1.387566,
        1.387777, 1.39303, 1.406789, 1.427726, 1.4525105
      };

  private static final int NB_DSC_EURUSD_QUOTES = DSC_EURUSD_MARKET_FORWARD.length;
  private static final double[] DSC_EURUSD_MARKET_QUOTES = new double[NB_DSC_EURUSD_QUOTES];

  static {
    for (int loopquote = 0; loopquote < NB_DSC_EURUSD_QUOTES; loopquote++) {
      DSC_EURUSD_MARKET_QUOTES[loopquote] = DSC_EURUSD_MARKET_FORWARD[loopquote] - FX_EURUSD;
    }
  }
  /** Generators for the dsc FX curve */
  private static final GeneratorInstrument<? extends GeneratorAttribute>[] DSC_EURUSD_GENERATORS =
      new GeneratorInstrument<?>[] {
        GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD,
            GENERATOR_FX_EURUSD,
        GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD, GENERATOR_FX_EURUSD,
            GENERATOR_FX_EURUSD
      };
  /** Tenors for the dsc FX curve */
  private static final Period[] DSC_EURUSD_TENOR =
      new Period[] {
        Period.ofMonths(1), Period.ofMonths(2), Period.ofMonths(3), Period.ofMonths(6),
            Period.ofMonths(9),
        Period.ofYears(1), Period.ofYears(2), Period.ofYears(3), Period.ofYears(4),
            Period.ofYears(5)
      };

  private static final GeneratorAttribute[] DSC_EURUSD_ATTR =
      new GeneratorAttribute[DSC_EUR_TENOR.length];

  static {
    for (int loopins = 0; loopins < DSC_EURUSD_TENOR.length; loopins++) {
      DSC_EURUSD_ATTR[loopins] = new GeneratorAttributeFX(DSC_EURUSD_TENOR[loopins], FX_MATRIX);
    }
  }

  /** Standard USD discounting curve instrument definitions */
  private static final InstrumentDefinition<?>[] DEFINITIONS_DSC_USD;
  /** Standard EUR discounting curve instrument definitions */
  private static final InstrumentDefinition<?>[] DEFINITIONS_DSC_EUR;
  /** Standard EUR discounting curve instrument definitions */
  private static final InstrumentDefinition<?>[] DEFINITIONS_DSC_FX;

  /** Units of curves */
  private static final int[] NB_UNITS = new int[] {2, 2, 2};

  private static final int NB_BLOCKS = NB_UNITS.length;
  private static final InstrumentDefinition<?>[][][][] DEFINITIONS_UNITS =
      new InstrumentDefinition<?>[NB_BLOCKS][][][];
  private static final GeneratorYDCurve[][][] GENERATORS_UNITS =
      new GeneratorYDCurve[NB_BLOCKS][][];
  private static final String[][][] NAMES_UNITS = new String[NB_BLOCKS][][];

  private static final MulticurveProviderDiscount MULTICURVE_KNOWN_DATA =
      new MulticurveProviderDiscount(FX_MATRIX);

  private static final LinkedHashMap<String, Currency> DSC_MAP = new LinkedHashMap<>();
  private static final LinkedHashMap<String, IndexON[]> FWD_ON_MAP = new LinkedHashMap<>();
  private static final LinkedHashMap<String, IborIndex[]> FWD_IBOR_MAP = new LinkedHashMap<>();

  static {
    DEFINITIONS_DSC_USD = getDefinitions(DSC_USD_MARKET_QUOTES, DSC_USD_GENERATORS, DSC_USD_ATTR);
    DEFINITIONS_DSC_EUR = getDefinitions(DSC_EUR_MARKET_QUOTES, DSC_EUR_GENERATORS, DSC_EUR_ATTR);
    DEFINITIONS_DSC_FX =
        getDefinitions(DSC_EURUSD_MARKET_QUOTES, DSC_EURUSD_GENERATORS, DSC_EURUSD_ATTR);
    for (int loopblock = 0; loopblock < NB_BLOCKS; loopblock++) {
      DEFINITIONS_UNITS[loopblock] = new InstrumentDefinition<?>[NB_UNITS[loopblock]][][];
      GENERATORS_UNITS[loopblock] = new GeneratorYDCurve[NB_UNITS[loopblock]][];
      NAMES_UNITS[loopblock] = new String[NB_UNITS[loopblock]][];
    }
    DEFINITIONS_UNITS[0] = new InstrumentDefinition<?>[NB_UNITS[0]][][];
    DEFINITIONS_UNITS[0][0] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_USD};
    DEFINITIONS_UNITS[0][1] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_EUR};
    DEFINITIONS_UNITS[1] = new InstrumentDefinition<?>[NB_UNITS[0]][][];
    DEFINITIONS_UNITS[1][0] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_USD};
    DEFINITIONS_UNITS[1][1] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_FX};
    DEFINITIONS_UNITS[2] = new InstrumentDefinition<?>[NB_UNITS[0]][][];
    DEFINITIONS_UNITS[2][0] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_EUR};
    DEFINITIONS_UNITS[2][1] = new InstrumentDefinition<?>[][] {DEFINITIONS_DSC_FX};
    final GeneratorYDCurve genIntLin =
        new GeneratorCurveYieldInterpolated(MATURITY_CALCULATOR, INTERPOLATOR_LINEAR);
    GENERATORS_UNITS[0] = new GeneratorYDCurve[NB_UNITS[0]][];
    GENERATORS_UNITS[0][0] = new GeneratorYDCurve[] {genIntLin};
    GENERATORS_UNITS[0][1] = new GeneratorYDCurve[] {genIntLin};
    GENERATORS_UNITS[1] = new GeneratorYDCurve[NB_UNITS[0]][];
    GENERATORS_UNITS[1][0] = new GeneratorYDCurve[] {genIntLin};
    GENERATORS_UNITS[1][1] = new GeneratorYDCurve[] {genIntLin};
    GENERATORS_UNITS[2] = new GeneratorYDCurve[NB_UNITS[0]][];
    GENERATORS_UNITS[2][0] = new GeneratorYDCurve[] {genIntLin};
    GENERATORS_UNITS[2][1] = new GeneratorYDCurve[] {genIntLin};
    NAMES_UNITS[0] = new String[NB_UNITS[0]][];
    NAMES_UNITS[0][0] = new String[] {CURVE_NAME_DSC_USD};
    NAMES_UNITS[0][1] = new String[] {CURVE_NAME_DSC_EUR};
    NAMES_UNITS[1] = new String[NB_UNITS[0]][];
    NAMES_UNITS[1][0] = new String[] {CURVE_NAME_DSC_USD};
    NAMES_UNITS[1][1] = new String[] {CURVE_NAME_DSC_EUR};
    NAMES_UNITS[2] = new String[NB_UNITS[0]][];
    NAMES_UNITS[2][0] = new String[] {CURVE_NAME_DSC_EUR};
    NAMES_UNITS[2][1] = new String[] {CURVE_NAME_DSC_USD};
    DSC_MAP.put(CURVE_NAME_DSC_USD, USD);
    DSC_MAP.put(CURVE_NAME_DSC_EUR, EUR);
    FWD_ON_MAP.put(CURVE_NAME_DSC_USD, new IndexON[] {INDEX_ON_USD});
    FWD_ON_MAP.put(CURVE_NAME_DSC_EUR, new IndexON[] {INDEX_ON_EUR});
  }

  @SuppressWarnings("unchecked")
  public static InstrumentDefinition<?>[] getDefinitions(
      final double[] marketQuotes,
      @SuppressWarnings("rawtypes") final GeneratorInstrument[] generators,
      final GeneratorAttribute[] attribute) {
    final InstrumentDefinition<?>[] definitions = new InstrumentDefinition<?>[marketQuotes.length];
    for (int loopmv = 0; loopmv < marketQuotes.length; loopmv++) {
      definitions[loopmv] =
          generators[loopmv].generateInstrument(
              NOW, marketQuotes[loopmv], NOTIONAL, attribute[loopmv]);
    }
    return definitions;
  }

  private static List<Pair<MulticurveProviderDiscount, CurveBuildingBlockBundle>>
      CURVES_PAR_SPREAD_MQ_WITHOUT_TODAY_BLOCK = new ArrayList<>();

  // Calculators
  private static final ParSpreadMarketQuoteDiscountingCalculator PSMQDC =
      ParSpreadMarketQuoteDiscountingCalculator.getInstance();
  private static final ParSpreadMarketQuoteCurveSensitivityDiscountingCalculator PSMQCSDC =
      ParSpreadMarketQuoteCurveSensitivityDiscountingCalculator.getInstance();

  private static final MulticurveDiscountBuildingRepository CURVE_BUILDING_REPOSITORY =
      new MulticurveDiscountBuildingRepository(TOLERANCE_ROOT, TOLERANCE_ROOT, STEP_MAX);

  static {
    for (int loopblock = 0; loopblock < NB_BLOCKS; loopblock++) {
      CURVES_PAR_SPREAD_MQ_WITHOUT_TODAY_BLOCK.add(
          makeCurvesFromDefinitions(
              DEFINITIONS_UNITS[loopblock],
              GENERATORS_UNITS[loopblock],
              NAMES_UNITS[loopblock],
              MULTICURVE_KNOWN_DATA,
              PSMQDC,
              PSMQCSDC,
              false));
    }
  }

  public static Pair<MulticurveProviderDiscount, CurveBuildingBlockBundle> getCurvesEUROisUSDOis() {
    return CURVES_PAR_SPREAD_MQ_WITHOUT_TODAY_BLOCK.get(0);
  }

  public static Pair<MulticurveProviderDiscount, CurveBuildingBlockBundle> getCurvesUSDOisEURFx() {
    return CURVES_PAR_SPREAD_MQ_WITHOUT_TODAY_BLOCK.get(1);
  }

  public static Pair<MulticurveProviderDiscount, CurveBuildingBlockBundle> getCurvesEUROisUSDFx() {
    return CURVES_PAR_SPREAD_MQ_WITHOUT_TODAY_BLOCK.get(2);
  }

  /**
   * Returns the array of overnight index used in the curve data set.
   *
   * @return The array: USDFEDFUND, EUREOINIA
   */
  public static IndexON[] indexONArray() {
    return new IndexON[] {USDFEDFUND, EUREONIA};
  }

  /**
   * Returns the array of calendars used in the curve data set.
   *
   * @return The array: NYC, TARGET
   */
  public static HolidayCalendar[] calendarArray() {
    return new HolidayCalendar[] {NYC, TARGET};
  }

  @SuppressWarnings("unchecked")
  private static Pair<MulticurveProviderDiscount, CurveBuildingBlockBundle>
      makeCurvesFromDefinitions(
          final InstrumentDefinition<?>[][][] definitions,
          final GeneratorYDCurve[][] curveGenerators,
          final String[][] curveNames,
          final MulticurveProviderDiscount knownData,
          final InstrumentDerivativeVisitor<ParameterProviderInterface, Double> calculator,
          final InstrumentDerivativeVisitor<ParameterProviderInterface, MulticurveSensitivity>
              sensitivityCalculator,
          final boolean withToday) {
    final int nUnits = definitions.length;
    final MultiCurveBundle<GeneratorYDCurve>[] curveBundles = new MultiCurveBundle[nUnits];
    for (int i = 0; i < nUnits; i++) {
      final int nCurves = definitions[i].length;
      final SingleCurveBundle<GeneratorYDCurve>[] singleCurves = new SingleCurveBundle[nCurves];
      for (int j = 0; j < nCurves; j++) {
        final int nInstruments = definitions[i][j].length;
        final InstrumentDerivative[] derivatives = new InstrumentDerivative[nInstruments];
        final double[] rates = new double[nInstruments];
        for (int k = 0; k < nInstruments; k++) {
          derivatives[k] = convert(definitions[i][j][k], withToday);
          rates[k] = initialGuess(definitions[i][j][k]);
        }
        final GeneratorYDCurve generator = curveGenerators[i][j].finalGenerator(derivatives);
        final double[] initialGuess = generator.initialGuess(rates);
        singleCurves[j] =
            new SingleCurveBundle<>(curveNames[i][j], derivatives, initialGuess, generator);
      }
      curveBundles[i] = new MultiCurveBundle<>(singleCurves);
    }
    return CURVE_BUILDING_REPOSITORY.makeCurvesFromDerivatives(
        curveBundles,
        knownData,
        DSC_MAP,
        FWD_IBOR_MAP,
        FWD_ON_MAP,
        calculator,
        sensitivityCalculator);
  }

  private static InstrumentDerivative convert(
      final InstrumentDefinition<?> instrument, final boolean withToday) {
    InstrumentDerivative ird;
    if (instrument instanceof SwapFixedONDefinition) {
      ird = ((SwapFixedONDefinition) instrument).toDerivative(NOW, getTSSwapFixedON(withToday));
    } else {
      ird = instrument.toDerivative(NOW);
    }
    return ird;
  }

  private static ZonedDateTimeDoubleTimeSeries[] getTSSwapFixedON(final Boolean withToday) {
    return withToday ? TS_FIXED_OIS_USD_WITH_TODAY : TS_FIXED_OIS_USD_WITHOUT_TODAY;
  }

  private static double initialGuess(final InstrumentDefinition<?> instrument) {
    if (instrument instanceof SwapFixedONDefinition) {
      return ((SwapFixedONDefinition) instrument).getFixedLeg().getNthPayment(0).getRate();
    }
    if (instrument instanceof SwapFixedIborDefinition) {
      return ((SwapFixedIborDefinition) instrument).getFixedLeg().getNthPayment(0).getRate();
    }
    if (instrument instanceof ForwardRateAgreementDefinition) {
      return ((ForwardRateAgreementDefinition) instrument).getRate();
    }
    if (instrument instanceof CashDefinition) {
      return ((CashDefinition) instrument).getRate();
    }
    if (instrument instanceof InterestRateFutureTransactionDefinition) {
      return 1 - ((InterestRateFutureTransactionDefinition) instrument).getTradePrice();
    }
    return 0.01;
  }
}
/**
 * Test for curve calibration in USD and EUR. The USD curve is obtained by OIS and the EUR one by FX
 * Swaps from USD.
 */
@Test
public class CalibrationZeroRateUsdEur2OisFxTest {

  private static final LocalDate VAL_DATE = LocalDate.of(2015, 11, 2);

  private static final CurveInterpolator INTERPOLATOR_LINEAR = CurveInterpolators.LINEAR;
  private static final CurveExtrapolator EXTRAPOLATOR_FLAT = CurveExtrapolators.FLAT;
  private static final DayCount CURVE_DC = ACT_365F;

  private static final String SCHEME = "CALIBRATION";

  /** Curve names */
  private static final String USD_DSCON_STR = "USD-DSCON-OIS";

  private static final CurveName USD_DSCON_CURVE_NAME = CurveName.of(USD_DSCON_STR);
  private static final String EUR_DSC_STR = "EUR-DSC-FX";
  private static final CurveName EUR_DSC_CURVE_NAME = CurveName.of(EUR_DSC_STR);
  /** Curves associations to currencies and indices. */
  private static final Map<CurveName, Currency> DSC_NAMES = new HashMap<>();

  private static final Map<CurveName, Set<Index>> IDX_NAMES = new HashMap<>();
  private static final Map<Index, LocalDateDoubleTimeSeries> TS = new HashMap<>();

  static {
    DSC_NAMES.put(USD_DSCON_CURVE_NAME, USD);
    Set<Index> usdFedFundSet = new HashSet<>();
    usdFedFundSet.add(USD_FED_FUND);
    IDX_NAMES.put(USD_DSCON_CURVE_NAME, usdFedFundSet);
  }

  /** Data FX * */
  private static final FxRate FX_RATE_EUR_USD = FxRate.of(EUR, USD, 1.10);
  /** Data for USD-DSCON curve */
  /* Market values */
  private static final double[] USD_DSC_MARKET_QUOTES =
      new double[] {
        0.0016, 0.0022, 0.0013, 0.0016, 0.0020, 0.0026, 0.0033, 0.0039, 0.0053, 0.0066, 0.0090,
        0.0111
      };

  private static final int USD_DSC_NB_NODES = USD_DSC_MARKET_QUOTES.length;
  private static final String[] USD_DSC_ID_VALUE =
      new String[] {
        "USD-ON",
        "USD-TN",
        "USD-OIS-1M",
        "USD-OIS-2M",
        "USD-OIS-3M",
        "USD-OIS-6M",
        "USD-OIS-9M",
        "USD-OIS-1Y",
        "USD-OIS-18M",
        "USD-OIS-2Y",
        "USD-OIS-3Y",
        "USD-OIS-4Y"
      };
  /* Nodes */
  private static final CurveNode[] USD_DSC_NODES = new CurveNode[USD_DSC_NB_NODES];
  /* Tenors */
  private static final int[] USD_DSC_DEPO_OFFSET = new int[] {0, 1};
  private static final int USD_DSC_NB_DEPO_NODES = USD_DSC_DEPO_OFFSET.length;
  private static final Period[] USD_DSC_OIS_TENORS =
      new Period[] {
        Period.ofMonths(1), Period.ofMonths(2), Period.ofMonths(3), Period.ofMonths(6),
            Period.ofMonths(9),
        Period.ofYears(1), Period.ofMonths(18), Period.ofYears(2), Period.ofYears(3),
            Period.ofYears(4)
      };
  private static final int USD_DSC_NB_OIS_NODES = USD_DSC_OIS_TENORS.length;

  static {
    USD_DSC_NODES[0] =
        TermDepositCurveNode.of(
            TermDepositTemplate.of(Period.ofDays(1), USD_DEPOSIT_T0),
            QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[0])));
    USD_DSC_NODES[1] =
        TermDepositCurveNode.of(
            TermDepositTemplate.of(Period.ofDays(1), USD_DEPOSIT_T1),
            QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[1])));
    for (int i = 0; i < USD_DSC_NB_OIS_NODES; i++) {
      USD_DSC_NODES[USD_DSC_NB_DEPO_NODES + i] =
          FixedOvernightSwapCurveNode.of(
              FixedOvernightSwapTemplate.of(
                  Period.ZERO, Tenor.of(USD_DSC_OIS_TENORS[i]), USD_FIXED_1Y_FED_FUND_OIS),
              QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[USD_DSC_NB_DEPO_NODES + i])));
    }
  }
  /** Data for EUR-DSC curve */
  /* Market values */
  private static final double[] EUR_DSC_MARKET_QUOTES =
      new double[] {
        0.0004, 0.0012, 0.0019, 0.0043, 0.0074,
        0.0109, 0.0193, 0.0294, 0.0519, 0.0757
      };

  private static final int EUR_DSC_NB_NODES = EUR_DSC_MARKET_QUOTES.length;
  private static final String[] EUR_DSC_ID_VALUE =
      new String[] {
        "EUR-USD-FX-1M", "EUR-USD-FX-2M", "EUR-USD-FX-3M", "EUR-USD-FX-6M", "EUR-USD-FX-9M",
        "EUR-USD-FX-1Y", "EUR-USD-FX-18M", "EUR-USD-FX-2Y", "EUR-USD-FX-3Y", "EUR-USD-FX-4Y"
      };
  /* Nodes */
  private static final CurveNode[] EUR_DSC_NODES = new CurveNode[EUR_DSC_NB_NODES];
  /* Tenors */
  private static final Period[] EUR_DSC_FX_TENORS =
      new Period[] {
        Period.ofMonths(1), Period.ofMonths(2), Period.ofMonths(3), Period.ofMonths(6),
            Period.ofMonths(9),
        Period.ofYears(1), Period.ofMonths(18), Period.ofYears(2), Period.ofYears(3),
            Period.ofYears(4)
      };
  private static final int EUR_DSC_NB_FX_NODES = EUR_DSC_FX_TENORS.length;

  static {
    for (int i = 0; i < EUR_DSC_NB_FX_NODES; i++) {
      EUR_DSC_NODES[i] =
          FxSwapCurveNode.of(
              FxSwapTemplate.of(EUR_DSC_FX_TENORS[i], EUR_USD),
              QuoteKey.of(StandardId.of(SCHEME, EUR_DSC_ID_VALUE[i])));
    }
  }

  /** All quotes for the curve calibration */
  private static final ImmutableMarketData ALL_QUOTES;

  static {
    Map<MarketDataKey<?>, Object> map = new HashMap<>();
    for (int i = 0; i < USD_DSC_NB_NODES; i++) {
      map.put(QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[i])), USD_DSC_MARKET_QUOTES[i]);
    }
    for (int i = 0; i < EUR_DSC_NB_NODES; i++) {
      map.put(QuoteKey.of(StandardId.of(SCHEME, EUR_DSC_ID_VALUE[i])), EUR_DSC_MARKET_QUOTES[i]);
    }
    map.put(FxRateKey.of(EUR, USD), FX_RATE_EUR_USD);
    ALL_QUOTES = ImmutableMarketData.of(map);
  }

  private static final DiscountingSwapProductPricer SWAP_PRICER =
      DiscountingSwapProductPricer.DEFAULT;
  private static final DiscountingTermDepositProductPricer DEPO_PRICER =
      DiscountingTermDepositProductPricer.DEFAULT;
  private static final DiscountingFxSwapProductPricer FX_PRICER =
      DiscountingFxSwapProductPricer.DEFAULT;
  private static final MarketQuoteSensitivityCalculator MQC =
      MarketQuoteSensitivityCalculator.DEFAULT;

  private static final CalibrationMeasures CALIBRATION_MEASURES = CalibrationMeasures.DEFAULT;
  private static final CurveCalibrator CALIBRATOR =
      CurveCalibrator.of(1e-9, 1e-9, 100, CALIBRATION_MEASURES);

  // Constants
  private static final double TOLERANCE_PV = 1.0E-6;
  private static final double TOLERANCE_PV_DELTA = 1.0E+3;

  private static final CurveGroupName CURVE_GROUP_NAME = CurveGroupName.of("USD-DSCON-EUR-DSC");
  private static final InterpolatedNodalCurveDefinition USD_DSC_CURVE_DEFN =
      InterpolatedNodalCurveDefinition.builder()
          .name(USD_DSCON_CURVE_NAME)
          .xValueType(ValueType.YEAR_FRACTION)
          .yValueType(ValueType.ZERO_RATE)
          .dayCount(CURVE_DC)
          .interpolator(INTERPOLATOR_LINEAR)
          .extrapolatorLeft(EXTRAPOLATOR_FLAT)
          .extrapolatorRight(EXTRAPOLATOR_FLAT)
          .nodes(USD_DSC_NODES)
          .build();
  private static final InterpolatedNodalCurveDefinition EUR_DSC_CURVE_DEFN =
      InterpolatedNodalCurveDefinition.builder()
          .name(EUR_DSC_CURVE_NAME)
          .xValueType(ValueType.YEAR_FRACTION)
          .yValueType(ValueType.ZERO_RATE)
          .dayCount(CURVE_DC)
          .interpolator(INTERPOLATOR_LINEAR)
          .extrapolatorLeft(EXTRAPOLATOR_FLAT)
          .extrapolatorRight(EXTRAPOLATOR_FLAT)
          .nodes(EUR_DSC_NODES)
          .build();
  private static final CurveGroupDefinition CURVE_GROUP_CONFIG =
      CurveGroupDefinition.builder()
          .name(CURVE_GROUP_NAME)
          .addCurve(USD_DSC_CURVE_DEFN, USD, USD_FED_FUND)
          .addDiscountCurve(EUR_DSC_CURVE_DEFN, EUR)
          .build();

  // -------------------------------------------------------------------------
  public void calibration_present_value_oneGroup() {
    ImmutableRatesProvider result =
        CALIBRATOR.calibrate(CURVE_GROUP_CONFIG, VAL_DATE, ALL_QUOTES, TS);
    assertPresentValue(result);
  }

  private void assertPresentValue(ImmutableRatesProvider result) {
    // Test PV USD;
    List<Trade> usdTrades = new ArrayList<>();
    for (int i = 0; i < USD_DSC_NODES.length; i++) {
      usdTrades.add(USD_DSC_NODES[i].trade(VAL_DATE, ALL_QUOTES));
    }
    // Depo
    for (int i = 0; i < USD_DSC_NB_DEPO_NODES; i++) {
      CurrencyAmount pvDep =
          DEPO_PRICER.presentValue(((TermDepositTrade) usdTrades.get(i)).getProduct(), result);
      assertEquals(pvDep.getAmount(), 0.0, TOLERANCE_PV);
    }
    // OIS
    for (int i = 0; i < USD_DSC_NB_OIS_NODES; i++) {
      MultiCurrencyAmount pvOis =
          SWAP_PRICER.presentValue(
              ((SwapTrade) usdTrades.get(USD_DSC_NB_DEPO_NODES + i)).getProduct(), result);
      assertEquals(pvOis.getAmount(USD).getAmount(), 0.0, TOLERANCE_PV);
    }
    // Test PV EUR;
    List<Trade> eurTrades = new ArrayList<>();
    for (int i = 0; i < EUR_DSC_NODES.length; i++) {
      eurTrades.add(EUR_DSC_NODES[i].trade(VAL_DATE, ALL_QUOTES));
    }
    // Depo
    for (int i = 0; i < EUR_DSC_NB_FX_NODES; i++) {
      MultiCurrencyAmount pvFx =
          FX_PRICER.presentValue(((FxSwapTrade) eurTrades.get(i)).getProduct(), result);
      assertEquals(pvFx.convertedTo(USD, result).getAmount(), 0.0, TOLERANCE_PV);
    }
  }

  public void calibration_market_quote_sensitivity_one_group() {
    double shift = 1.0E-6;
    Function<MarketData, ImmutableRatesProvider> f =
        ov -> CALIBRATOR.calibrate(CURVE_GROUP_CONFIG, VAL_DATE, ov, TS);
    calibration_market_quote_sensitivity_check(f, shift);
  }

  private void calibration_market_quote_sensitivity_check(
      Function<MarketData, ImmutableRatesProvider> calibrator, double shift) {

    double notional = 100_000_000.0;
    double fx = 1.1111;
    double fxPts = 0.0012;
    FxSwapTrade trade =
        EUR_USD.toTrade(
            VAL_DATE, Period.ofWeeks(6), Period.ofMonths(5), BuySell.BUY, notional, fx, fxPts);
    ImmutableRatesProvider result =
        CALIBRATOR.calibrate(CURVE_GROUP_CONFIG, VAL_DATE, ALL_QUOTES, TS);
    PointSensitivities pts = FX_PRICER.presentValueSensitivity(trade.getProduct(), result);
    CurveCurrencyParameterSensitivities ps = result.curveParameterSensitivity(pts);
    CurveCurrencyParameterSensitivities mqs = MQC.sensitivity(ps, result);
    double pvUsd = FX_PRICER.presentValue(trade.getProduct(), result).getAmount(USD).getAmount();
    double pvEur = FX_PRICER.presentValue(trade.getProduct(), result).getAmount(EUR).getAmount();
    double[] mqsUsd1Computed =
        mqs.getSensitivity(USD_DSCON_CURVE_NAME, USD).getSensitivity().toArray();
    for (int i = 0; i < USD_DSC_NB_NODES; i++) {
      Map<MarketDataKey<?>, Object> map = new HashMap<>(ALL_QUOTES.getValues());
      map.put(
          QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[i])),
          USD_DSC_MARKET_QUOTES[i] + shift);
      ImmutableMarketData marketData = ImmutableMarketData.of(map);
      ImmutableRatesProvider rpShifted = calibrator.apply(marketData);
      double pvS = FX_PRICER.presentValue(trade.getProduct(), rpShifted).getAmount(USD).getAmount();
      assertEquals(mqsUsd1Computed[i], (pvS - pvUsd) / shift, TOLERANCE_PV_DELTA);
    }
    double[] mqsUsd2Computed =
        mqs.getSensitivity(USD_DSCON_CURVE_NAME, EUR).getSensitivity().toArray();
    for (int i = 0; i < USD_DSC_NB_NODES; i++) {
      Map<MarketDataKey<?>, Object> map = new HashMap<>(ALL_QUOTES.getValues());
      map.put(
          QuoteKey.of(StandardId.of(SCHEME, USD_DSC_ID_VALUE[i])),
          USD_DSC_MARKET_QUOTES[i] + shift);
      ImmutableMarketData ov = ImmutableMarketData.of(map);
      ImmutableRatesProvider rpShifted = calibrator.apply(ov);
      double pvS = FX_PRICER.presentValue(trade.getProduct(), rpShifted).getAmount(EUR).getAmount();
      assertEquals(mqsUsd2Computed[i], (pvS - pvEur) / shift, TOLERANCE_PV_DELTA);
    }
    double[] mqsEur1Computed =
        mqs.getSensitivity(EUR_DSC_CURVE_NAME, USD).getSensitivity().toArray();
    for (int i = 0; i < EUR_DSC_NB_NODES; i++) {
      assertEquals(mqsEur1Computed[i], 0.0, TOLERANCE_PV_DELTA);
    }
    double[] mqsEur2Computed =
        mqs.getSensitivity(EUR_DSC_CURVE_NAME, EUR).getSensitivity().toArray();
    for (int i = 0; i < EUR_DSC_NB_NODES; i++) {
      Map<MarketDataKey<?>, Object> map = new HashMap<>(ALL_QUOTES.getValues());
      map.put(
          QuoteKey.of(StandardId.of(SCHEME, EUR_DSC_ID_VALUE[i])),
          EUR_DSC_MARKET_QUOTES[i] + shift);
      ImmutableMarketData marketData = ImmutableMarketData.of(map);
      ImmutableRatesProvider rpShifted = calibrator.apply(marketData);
      double pvS = FX_PRICER.presentValue(trade.getProduct(), rpShifted).getAmount(EUR).getAmount();
      assertEquals(mqsEur2Computed[i], (pvS - pvEur) / shift, TOLERANCE_PV_DELTA, "Node " + i);
    }
  }
}
/** Test. */
@Test
public class CdsDatesLogicTest {

  private static final Period[] TENORS =
      new Period[] {
        Period.ofMonths(6), Period.ofYears(1), Period.ofYears(2),
        Period.ofYears(3), Period.ofYears(5), Period.ofYears(10)
      };

  public void isCdsTest() {
    LocalDate date = LocalDate.of(2013, Month.MARCH, 20);
    assertTrue(isCdsDate(date));

    date = LocalDate.of(2013, Month.APRIL, 20);
    assertFalse(isCdsDate(date));

    date = LocalDate.of(2013, Month.DECEMBER, 23);
    assertFalse(isCdsDate(date));
  }

  public void onCdsDateTest() {
    final LocalDate today = LocalDate.of(2013, Month.MARCH, 20);
    final LocalDate nextCds = getNextCdsDate(today);
    final LocalDate[] dates = getCdsDateSet(nextCds, TENORS);
    assertEquals(dates.length, TENORS.length);
    assertEquals(LocalDate.of(2013, Month.DECEMBER, 20), dates[0]);
  }

  public void cdsSetTest() {
    final LocalDate date = LocalDate.of(2013, Month.MARCH, 20);
    final int n = 5;
    final LocalDate[] cdsDates = getCdsDateSet(date, n);
    assertTrue(cdsDates.length == n);
    assertEquals(date, cdsDates[0]);
    assertEquals(LocalDate.of(2013, Month.JUNE, 20), cdsDates[1]);
    assertEquals(LocalDate.of(2013, Month.SEPTEMBER, 20), cdsDates[2]);
    assertEquals(LocalDate.of(2013, Month.DECEMBER, 20), cdsDates[3]);
    assertEquals(LocalDate.of(2014, Month.MARCH, 20), cdsDates[4]);
  }

  public void nonCdsDateTest() {
    final LocalDate today = LocalDate.of(2013, Month.MARCH, 26);
    final LocalDate nextCds = getNextCdsDate(today);
    final LocalDate[] dates = getCdsDateSet(nextCds, TENORS);
    assertEquals(dates.length, TENORS.length);
    assertEquals(LocalDate.of(2013, Month.DECEMBER, 20), dates[0]);
  }

  public void cdsDateM1Test() {
    final LocalDate today = LocalDate.of(2013, Month.MARCH, 19);
    final LocalDate nextCds = getNextCdsDate(today);
    final LocalDate[] dates = getCdsDateSet(nextCds, TENORS);
    assertEquals(dates.length, TENORS.length);
    assertEquals(LocalDate.of(2013, Month.SEPTEMBER, 20), dates[0]);
  }

  public void stepinCdsDateM2Test() {
    final LocalDate today = LocalDate.of(2013, Month.MARCH, 18);
    final LocalDate nextCds = getNextCdsDate(today);
    final LocalDate[] dates = getCdsDateSet(nextCds, TENORS);
    assertEquals(dates.length, TENORS.length);
    assertEquals(LocalDate.of(2013, Month.SEPTEMBER, 20), dates[0]);
  }

  public void nextCdsTest() {
    LocalDate today = LocalDate.of(2011, Month.JUNE, 21);
    LocalDate nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.SEPTEMBER, 20), nextCds);

    today = LocalDate.of(2011, Month.JUNE, 20);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.SEPTEMBER, 20), nextCds);

    today = LocalDate.of(2011, Month.DECEMBER, 20);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2012, Month.MARCH, 20), nextCds);

    today = LocalDate.of(2011, Month.JUNE, 18);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.JUNE, 20), nextCds);

    today = LocalDate.of(1976, Month.JULY, 30);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(1976, Month.SEPTEMBER, 20), nextCds);

    today = LocalDate.of(1977, Month.FEBRUARY, 13);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(1977, Month.MARCH, 20), nextCds);

    today = LocalDate.of(2013, Month.MARCH, 1);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2013, Month.MARCH, 20), nextCds);

    today = LocalDate.of(2013, Month.DECEMBER, 25);
    nextCds = getNextCdsDate(today);
    assertEquals(LocalDate.of(2014, Month.MARCH, 20), nextCds);
  }

  public void prevCdsTest() {
    LocalDate today = LocalDate.of(2011, Month.JUNE, 21);
    LocalDate prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.JUNE, 20), prevCds);

    today = LocalDate.of(2011, Month.JUNE, 20);
    prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.MARCH, 20), prevCds);

    prevCds = getPreviousCdsDate(prevCds);
    assertEquals(LocalDate.of(2010, Month.DECEMBER, 20), prevCds);

    today = LocalDate.of(2011, Month.JUNE, 18);
    prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(2011, Month.MARCH, 20), prevCds);

    today = LocalDate.of(1976, Month.JULY, 30);
    prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(1976, Month.JUNE, 20), prevCds);

    today = LocalDate.of(1977, Month.FEBRUARY, 13);
    prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(1976, Month.DECEMBER, 20), prevCds);

    today = LocalDate.of(2013, Month.MARCH, 1);
    prevCds = getPreviousCdsDate(today);
    assertEquals(LocalDate.of(2012, Month.DECEMBER, 20), prevCds);
  }

  public void isIndexRollDateTest() {
    LocalDate date0 = LocalDate.of(2013, 3, 14);
    LocalDate date1 = LocalDate.of(2013, 6, 20);
    LocalDate date2 = LocalDate.of(2013, 3, 20);
    LocalDate date3 = LocalDate.of(2013, 9, 20);

    assertFalse(isIndexRollDate(date0));
    assertFalse(isIndexRollDate(date1));
    assertTrue(isIndexRollDate(date2));
    assertTrue(isIndexRollDate(date3));
  }

  public void getNextIndexRollDateTest() {
    final LocalDate[] dates =
        new LocalDate[] {
          LocalDate.of(2013, 3, 14),
          LocalDate.of(2013, 6, 20),
          LocalDate.of(2013, 3, 20),
          LocalDate.of(2013, 9, 20),
          LocalDate.of(2013, 1, 21),
          LocalDate.of(2013, 3, 21),
          LocalDate.of(2013, 9, 19),
          LocalDate.of(2013, 9, 21),
          LocalDate.of(2013, 11, 21)
        };

    final LocalDate[] datesExp =
        new LocalDate[] {
          LocalDate.of(2013, 3, 20),
          LocalDate.of(2013, 9, 20),
          LocalDate.of(2013, 9, 20),
          LocalDate.of(2014, 3, 20),
          LocalDate.of(2013, 3, 20),
          LocalDate.of(2013, 9, 20),
          LocalDate.of(2013, 9, 20),
          LocalDate.of(2014, 3, 20),
          LocalDate.of(2014, 3, 20)
        };

    for (int i = 0; i < dates.length; ++i) {
      assertEquals(getNextIndexRollDate(dates[i]), datesExp[i]);
    }
  }

  // -------------------------------------------------------------------------
  public void coverage() {
    coverPrivateConstructor(CdsDatesLogic.class);
  }
}
/** Tests related to the construction of CMS cap/floor. */
@Test
public class AnnuityCapFloorCMSDefinitionTest {
  private static final Currency CUR = Currency.EUR;
  private static final HolidayCalendar CALENDAR = HolidayCalendars.SAT_SUN;
  // Ibor index
  private static final BusinessDayConvention BUSINESS_DAY =
      BusinessDayConventions.MODIFIED_FOLLOWING;
  private static final boolean IS_EOM = true;
  private static final Period IBOR_TENOR = Period.ofMonths(3);
  private static final int IBOR_SETTLEMENT_DAYS = 2;
  private static final DayCount IBOR_DAY_COUNT = DayCounts.ACT_360;
  private static final IborIndex IBOR_INDEX =
      new IborIndex(
          CUR, IBOR_TENOR, IBOR_SETTLEMENT_DAYS, IBOR_DAY_COUNT, BUSINESS_DAY, IS_EOM, "Ibor");
  // CMS 10Y
  private static final Period CMS_TENOR = Period.ofYears(10);
  private static final Period FIXED_PAYMENT_PERIOD = Period.ofMonths(6);
  private static final DayCount FIXED_DAY_COUNT = DayCounts.THIRTY_U_360;
  private static final IndexSwap CMS_INDEX =
      new IndexSwap(FIXED_PAYMENT_PERIOD, FIXED_DAY_COUNT, IBOR_INDEX, CMS_TENOR, CALENDAR);
  // Annuity
  private static final ZonedDateTime START_DATE = DateUtils.getUTCDate(2011, 3, 17);
  private static final Period ANNUITY_TENOR = Period.ofYears(5);
  private static final ZonedDateTime MATURITY_DATE = START_DATE.plus(ANNUITY_TENOR);
  private static final double NOTIONAL = 100000000; // 100m
  private static final Period LEG_PAYMENT_PERIOD = Period.ofMonths(12);
  private static final DayCount LEG_DAY_COUNT = DayCounts.ACT_365F;
  private static final boolean IS_PAYER = true;
  private static final double STRIKE = 0.04;
  private static final boolean IS_CAP = true;
  private static final AnnuityCapFloorCMSDefinition CMS_LEG =
      AnnuityCapFloorCMSDefinition.from(
          START_DATE,
          MATURITY_DATE,
          NOTIONAL,
          CMS_INDEX,
          LEG_PAYMENT_PERIOD,
          LEG_DAY_COUNT,
          IS_PAYER,
          STRIKE,
          IS_CAP,
          CALENDAR);

  @Test
  public void dates() {
    final IborIndex fakeIborIndex12 =
        new IborIndex(
            CUR,
            LEG_PAYMENT_PERIOD,
            IBOR_SETTLEMENT_DAYS,
            LEG_DAY_COUNT,
            BUSINESS_DAY,
            IS_EOM,
            "Ibor");
    final AnnuityCouponIborDefinition iborLeg =
        AnnuityCouponIborDefinition.from(
            START_DATE, MATURITY_DATE, NOTIONAL, fakeIborIndex12, IS_PAYER, CALENDAR);
    for (int loopcpn = 0; loopcpn < iborLeg.getNumberOfPayments(); loopcpn++) {
      assertEquals(
          iborLeg.getNthPayment(loopcpn).getAccrualStartDate(),
          CMS_LEG.getNthPayment(loopcpn).getAccrualStartDate());
      assertEquals(
          iborLeg.getNthPayment(loopcpn).getAccrualEndDate(),
          CMS_LEG.getNthPayment(loopcpn).getAccrualEndDate());
      assertEquals(
          iborLeg.getNthPayment(loopcpn).getPaymentYearFraction(),
          CMS_LEG.getNthPayment(loopcpn).getPaymentYearFraction());
      assertEquals(
          iborLeg.getNthPayment(loopcpn).getPaymentDate(),
          CMS_LEG.getNthPayment(loopcpn).getPaymentDate());
      assertEquals(
          iborLeg.getNthPayment(loopcpn).getFixingDate(),
          CMS_LEG.getNthPayment(loopcpn).getFixingDate());
    }
  }

  @Test
  public void common() {
    for (int loopcpn = 0; loopcpn < CMS_LEG.getNumberOfPayments(); loopcpn++) {
      assertEquals(CMS_INDEX, CMS_LEG.getNthPayment(loopcpn).getCMSIndex());
      assertEquals(
          NOTIONAL * (IS_PAYER ? -1.0 : 1.0), CMS_LEG.getNthPayment(loopcpn).getNotional());
      assertEquals(STRIKE, CMS_LEG.getNthPayment(loopcpn).getStrike());
      assertEquals(IS_CAP, CMS_LEG.getNthPayment(loopcpn).isCap());
    }
    final AnnuityCapFloorCMSDefinition cmsCapReceiver =
        AnnuityCapFloorCMSDefinition.from(
            START_DATE,
            MATURITY_DATE,
            NOTIONAL,
            CMS_INDEX,
            LEG_PAYMENT_PERIOD,
            LEG_DAY_COUNT,
            !IS_PAYER,
            STRIKE,
            IS_CAP,
            CALENDAR);
    for (int loopcpn = 0; loopcpn < CMS_LEG.getNumberOfPayments(); loopcpn++) {
      assertEquals(CMS_INDEX, cmsCapReceiver.getNthPayment(loopcpn).getCMSIndex());
      assertEquals(
          -NOTIONAL * (IS_PAYER ? -1.0 : 1.0), cmsCapReceiver.getNthPayment(loopcpn).getNotional());
    }
  }
}