/** Tests related to the discounting method for bond security. */
public class BondSecurityFRDiscountingMethodTest {

  // Calculators
  private static final PresentValueCalculator PVC = PresentValueCalculator.getInstance();
  private static final BondSecurityDiscountingMethod METHOD =
      BondSecurityDiscountingMethod.getInstance();

  private static final double TOLERANCE_PRICE = 1.0E-8;

  private static final String REPO_TYPE = "General collateral";
  private static final Currency EUR = Currency.EUR;
  private static final Calendar TARGET = new MondayToFridayCalendar("TARGET");
  private static final DayCount DAY_COUNT_ACTACTICMA =
      DayCountFactory.INSTANCE.getDayCount("Actual/Actual ICMA");
  private static final BusinessDayConvention BUSINESS_DAY_FOL =
      BusinessDayConventionFactory.INSTANCE.getBusinessDayConvention("Following");
  private static final boolean IS_EOM_FIXED = false;

  // To derivatives
  private static final DayCount ACT_ACT =
      DayCountFactory.INSTANCE.getDayCount("Actual/Actual ISDA");
  private static final String CREDIT_CURVE_NAME = "Credit";
  private static final String REPO_CURVE_NAME = "Repo";
  private static final String FORWARD_CURVE_NAME = "Forward";
  private static final String[] CURVES_NAME = {
    CREDIT_CURVE_NAME, REPO_CURVE_NAME, FORWARD_CURVE_NAME
  };
  private static final YieldCurveBundle CURVES = TestsDataSetsSABR.createCurvesBond1();

  // FRTR 4 1/2 04/25/41 - ISIN:  FR0010773192
  private static final String ISSUER_FR = "FRANCE (GOVT OF)";
  private static final YieldConvention YIELD_CONVENTION_FRANCE =
      SimpleYieldConvention.FRANCE_COMPOUND_METHOD;
  private static final int SETTLEMENT_DAYS_FR = 3;
  private static final Period PAYMENT_TENOR_FR = Period.ofMonths(12);
  //  private static final int COUPON_PER_YEAR_FR = 1;
  private static final ZonedDateTime BOND_START_FR = DateUtils.getUTCDate(2009, 4, 25);
  private static final ZonedDateTime BOND_MATURITY_FR = DateUtils.getUTCDate(2041, 4, 25);
  private static final ZonedDateTime BOND_FIRSTCPN_FR = DateUtils.getUTCDate(2010, 4, 25);
  private static final double RATE_FR = 0.0450;
  private static final BondFixedSecurityDefinition BOND_FR_SECURITY_DEFINITION =
      BondFixedSecurityDefinition.from(
          EUR,
          BOND_START_FR,
          BOND_FIRSTCPN_FR,
          BOND_MATURITY_FR,
          PAYMENT_TENOR_FR,
          RATE_FR,
          SETTLEMENT_DAYS_FR,
          TARGET,
          DAY_COUNT_ACTACTICMA,
          BUSINESS_DAY_FOL,
          YIELD_CONVENTION_FRANCE,
          IS_EOM_FIXED,
          ISSUER_FR);
}
 /**
  * Compute the present value of a bond transaction from its yield-to-maturity.
  *
  * @param bond The bond transaction.
  * @param curves The curve bundle.
  * @param yield The bond yield.
  * @return The present value.
  */
 public double presentValueFromYield(
     final BondTransaction<? extends BondSecurity<? extends Payment, ? extends Coupon>> bond,
     final YieldCurveBundle curves,
     final double yield) {
   ArgumentChecker.notNull(bond, "Bond");
   ArgumentChecker.isTrue(
       bond instanceof BondFixedTransaction,
       "Present value from clean price only for fixed coupon bond");
   final BondFixedTransaction bondFixed = (BondFixedTransaction) bond;
   double cleanPrice = METHOD_SECURITY.cleanPriceFromYield(bondFixed.getBondStandard(), yield);
   return presentValueFromCleanPrice(bond, curves, cleanPrice);
 }
/** Class with methods related to bond transaction valued by discounting. */
public final class BondTransactionDiscountingMethod {

  private static final Logger LOGGER =
      LoggerFactory.getLogger(BondTransactionDiscountingMethod.class);
  /** The unique instance of the class. */
  private static final BondTransactionDiscountingMethod INSTANCE =
      new BondTransactionDiscountingMethod();

  /**
   * Return the class instance.
   *
   * @return The instance.
   */
  public static BondTransactionDiscountingMethod getInstance() {
    return INSTANCE;
  }

  /** Constructor */
  private BondTransactionDiscountingMethod() {}

  /** The present value calculator (for the different parts of the bond transaction). */
  private static final PresentValueCalculator PVC = PresentValueCalculator.getInstance();

  /** The present value calculator (for the different parts of the bond transaction). */
  private static final PresentValueCurveSensitivityCalculator PVSC =
      PresentValueCurveSensitivityCalculator.getInstance();

  private static final BondSecurityDiscountingMethod METHOD_SECURITY =
      BondSecurityDiscountingMethod.getInstance();

  /**
   * Compute the present value of a fixed coupon bond transaction.
   *
   * @param bond The bond transaction.
   * @param curves The curve bundle.
   * @return The present value.
   */
  public double presentValue(final BondFixedTransaction bond, final YieldCurveBundle curves) {
    final double pvNominal = bond.getBondTransaction().getNominal().accept(PVC, curves);
    final double pvCoupon = bond.getBondTransaction().getCoupon().accept(PVC, curves);
    final double settlementAmount =
        -(bond.getTransactionPrice()
                    * bond.getBondTransaction().getCoupon().getNthPayment(0).getNotional()
                + bond.getBondTransaction().getAccruedInterest())
            * bond.getQuantity();
    final PaymentFixed settlement =
        new PaymentFixed(
            bond.getBondTransaction().getCurrency(),
            bond.getBondTransaction().getSettlementTime(),
            settlementAmount,
            bond.getBondTransaction().getRepoCurveName());
    final double pvSettlement = settlement.accept(PVC, curves);
    return (pvNominal + pvCoupon) * bond.getQuantity() + pvSettlement;
  }

  /**
   * Compute the present value of a Ibor coupon bond (FRN) transaction.
   *
   * @param bond The bond transaction.
   * @param curves The curve bundle.
   * @return The present value.
   */
  public double presentValue(final BondIborTransaction bond, final YieldCurveBundle curves) {
    final double pvNominal = bond.getBondTransaction().getNominal().accept(PVC, curves);
    final double pvCoupon = bond.getBondTransaction().getCoupon().accept(PVC, curves);
    final double settlementAmount =
        bond.getTransactionPrice()
            * bond.getBondTransaction()
                .getCoupon()
                .getNthPayment(0)
                .getNotional(); // FIXME: add accrued.
    LOGGER.error("The FRN settlement amount does not include the accrued interests.");
    final PaymentFixed settlement =
        new PaymentFixed(
            bond.getBondTransaction().getCurrency(),
            bond.getBondTransaction().getSettlementTime(),
            settlementAmount,
            bond.getBondTransaction().getRepoCurveName());
    final double pvSettlement = settlement.accept(PVC, curves);
    return (pvNominal + pvCoupon) * bond.getQuantity() + pvSettlement;
  }

  /**
   * Compute the present value of a bond transaction from its clean price.
   *
   * @param bond The bond transaction.
   * @param curves The curve bundle.
   * @param cleanPrice The bond clean price.
   * @return The present value.
   */
  public double presentValueFromCleanPrice(
      final BondTransaction<? extends BondSecurity<? extends Payment, ? extends Coupon>> bond,
      final YieldCurveBundle curves,
      final double cleanPrice) {
    ArgumentChecker.isTrue(
        bond instanceof BondFixedTransaction,
        "Present value from clean price only for fixed coupon bond");
    final BondFixedTransaction bondFixed = (BondFixedTransaction) bond;
    final double dfSettle =
        curves
            .getCurve(bondFixed.getBondStandard().getRepoCurveName())
            .getDiscountFactor(bondFixed.getBondTransaction().getSettlementTime());
    final double pvPriceStandard =
        (cleanPrice * bondFixed.getNotionalStandard()
                + bondFixed.getBondStandard().getAccruedInterest())
            * dfSettle;
    final double pvNominalStandard = bond.getBondStandard().getNominal().accept(PVC, curves);
    final double pvCouponStandard = bond.getBondStandard().getCoupon().accept(PVC, curves);
    final double pvDiscountingStandard = (pvNominalStandard + pvCouponStandard);
    final double pvNominalTransaction = bond.getBondTransaction().getNominal().accept(PVC, curves);
    final double pvCouponTransaction = bond.getBondTransaction().getCoupon().accept(PVC, curves);
    final double pvDiscountingTransaction = (pvNominalTransaction + pvCouponTransaction);
    return (pvDiscountingTransaction - pvDiscountingStandard + pvPriceStandard)
        * bond.getQuantity();
  }

  /**
   * Compute the present value of a bond transaction from its yield-to-maturity.
   *
   * @param bond The bond transaction.
   * @param curves The curve bundle.
   * @param yield The bond yield.
   * @return The present value.
   */
  public double presentValueFromYield(
      final BondTransaction<? extends BondSecurity<? extends Payment, ? extends Coupon>> bond,
      final YieldCurveBundle curves,
      final double yield) {
    ArgumentChecker.notNull(bond, "Bond");
    ArgumentChecker.isTrue(
        bond instanceof BondFixedTransaction,
        "Present value from clean price only for fixed coupon bond");
    final BondFixedTransaction bondFixed = (BondFixedTransaction) bond;
    double cleanPrice = METHOD_SECURITY.cleanPriceFromYield(bondFixed.getBondStandard(), yield);
    return presentValueFromCleanPrice(bond, curves, cleanPrice);
  }

  /**
   * Compute the present value sensitivity of a bond transaction.
   *
   * @param bond The bond transaction.
   * @param curves The curve bundle.
   * @return The present value sensitivity.
   */
  public InterestRateCurveSensitivity presentValueSensitivity(
      final BondFixedTransaction bond, final YieldCurveBundle curves) {
    final InterestRateCurveSensitivity pvsNominal =
        new InterestRateCurveSensitivity(
            bond.getBondTransaction().getNominal().accept(PVSC, curves));
    final InterestRateCurveSensitivity pvsCoupon =
        new InterestRateCurveSensitivity(
            bond.getBondTransaction().getCoupon().accept(PVSC, curves));
    final double settlementAmount =
        -(bond.getTransactionPrice()
                    * bond.getBondTransaction().getCoupon().getNthPayment(0).getNotional()
                + bond.getBondTransaction().getAccruedInterest())
            * bond.getQuantity();
    final PaymentFixed settlement =
        new PaymentFixed(
            bond.getBondTransaction().getCurrency(),
            bond.getBondTransaction().getSettlementTime(),
            settlementAmount,
            bond.getBondTransaction().getRepoCurveName());
    final InterestRateCurveSensitivity pvsSettlement =
        new InterestRateCurveSensitivity(settlement.accept(PVSC, curves));
    return pvsNominal.plus(pvsCoupon).multipliedBy(bond.getQuantity()).plus(pvsSettlement);
  }

  public InterestRateCurveSensitivity presentValueSensitivity(
      final BondIborTransaction bond, final YieldCurveBundle curves) {
    final InterestRateCurveSensitivity pvsNominal =
        new InterestRateCurveSensitivity(
            bond.getBondTransaction().getNominal().accept(PVSC, curves));
    final InterestRateCurveSensitivity pvsCoupon =
        new InterestRateCurveSensitivity(
            bond.getBondTransaction().getCoupon().accept(PVSC, curves));
    final double settlementAmount =
        bond.getTransactionPrice()
            * bond.getBondTransaction()
                .getCoupon()
                .getNthPayment(0)
                .getNotional(); // FIXME: add accrued.
    LOGGER.error("The FRN settlement amount does not include the accrued interests.");
    final PaymentFixed settlement =
        new PaymentFixed(
            bond.getBondTransaction().getCurrency(),
            bond.getBondTransaction().getSettlementTime(),
            settlementAmount,
            bond.getBondTransaction().getRepoCurveName());
    final InterestRateCurveSensitivity pvsSettlement =
        new InterestRateCurveSensitivity(settlement.accept(PVSC, curves));
    return pvsNominal.plus(pvsCoupon).multipliedBy(bond.getQuantity()).plus(pvsSettlement);
  }
}