/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.index;

import static com.opengamma.strata.basics.currency.Currency.EUR;
import static com.opengamma.strata.basics.index.IborIndices.EUR_EURIBOR_3M;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

import java.time.LocalDate;

import org.junit.jupiter.api.Test;

import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.collect.DoubleArrayMath;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.pricer.model.HullWhiteOneFactorPiecewiseConstantParameters;
import com.opengamma.strata.pricer.model.HullWhiteOneFactorPiecewiseConstantParametersProvider;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.pricer.sensitivity.RatesFiniteDifferenceSensitivityCalculator;
import com.opengamma.strata.product.index.ResolvedIborFuture;

/**
 * Test {@link HullWhiteIborFutureProductPricer}.
 */
public class HullWhiteIborFutureProductPricerTest {

  private static final ReferenceData REF_DATA = ReferenceData.standard();
  private static final LocalDate VALUATION = LocalDate.of(2011, 5, 12);
  private static final HullWhiteOneFactorPiecewiseConstantParametersProvider HW_PROVIDER =
      HullWhiteIborFutureDataSet.createHullWhiteProvider(VALUATION);
  private static final ImmutableRatesProvider RATE_PROVIDER = HullWhiteIborFutureDataSet.createRatesProvider(VALUATION);
  private static final ResolvedIborFuture FUTURE = HullWhiteIborFutureDataSet.IBOR_FUTURE.resolve(REF_DATA);

  private static final double TOL = 1.0e-13;
  private static final double TOL_FD = 1.0e-6;
  private static final HullWhiteIborFutureProductPricer PRICER = HullWhiteIborFutureProductPricer.DEFAULT;
  private static final DiscountingIborFutureProductPricer PRICER_DSC = DiscountingIborFutureProductPricer.DEFAULT;
  private static final RatesFiniteDifferenceSensitivityCalculator FD_CAL =
      new RatesFiniteDifferenceSensitivityCalculator(TOL_FD);

  @Test
  public void test_price() {
    double computed = PRICER.price(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    LocalDate start = FUTURE.getIborRate().getObservation().getEffectiveDate();
    LocalDate end = FUTURE.getIborRate().getObservation().getMaturityDate();
    double fixingYearFraction = FUTURE.getIborRate().getObservation().getYearFraction();
    double convexity = HW_PROVIDER.futuresConvexityFactor(FUTURE.getLastTradeDate(), start, end);
    double forward = RATE_PROVIDER.iborIndexRates(EUR_EURIBOR_3M).rate(FUTURE.getIborRate().getObservation());
    double expected = 1d - convexity * forward + (1d - convexity) / fixingYearFraction;
    assertThat(computed).isCloseTo(expected, offset(TOL));
  }

  @Test
  public void test_parRate() {
    double computed = PRICER.parRate(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    double price = PRICER.price(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    assertThat(computed).isCloseTo(1d - price, offset(TOL));
  }

  @Test
  public void test_convexityAdjustment() {
    double computed = PRICER.convexityAdjustment(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    double priceHw = PRICER.price(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    double priceDsc = PRICER_DSC.price(FUTURE, RATE_PROVIDER); // no convexity adjustment
    assertThat(priceDsc + computed).isCloseTo(priceHw, offset(TOL));
  }

  @Test
  public void test_priceSensitivity() {
    PointSensitivities point = PRICER.priceSensitivityRates(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    CurrencyParameterSensitivities computed = RATE_PROVIDER.parameterSensitivity(point);
    CurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(RATE_PROVIDER, (p) -> CurrencyAmount.of(EUR, PRICER.price(FUTURE, (p), HW_PROVIDER)));
    assertThat(computed.equalWithTolerance(expected, TOL_FD)).isTrue();
  }

  @Test
  public void test_priceSensitivityHullWhiteParameter() {
    DoubleArray computed = PRICER.priceSensitivityModelParamsHullWhite(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    DoubleArray vols = HW_PROVIDER.getParameters().getVolatility();
    int size = vols.size();
    double[] expected = new double[size];
    for (int i = 0; i < size; ++i) {
      double[] volsUp = vols.toArray();
      double[] volsDw = vols.toArray();
      volsUp[i] += TOL_FD;
      volsDw[i] -= TOL_FD;
      HullWhiteOneFactorPiecewiseConstantParameters paramsUp = HullWhiteOneFactorPiecewiseConstantParameters.of(
          HW_PROVIDER.getParameters().getMeanReversion(), DoubleArray.copyOf(volsUp), HW_PROVIDER.getParameters()
              .getVolatilityTime().subArray(1, size));
      HullWhiteOneFactorPiecewiseConstantParameters paramsDw = HullWhiteOneFactorPiecewiseConstantParameters.of(
          HW_PROVIDER.getParameters().getMeanReversion(), DoubleArray.copyOf(volsDw), HW_PROVIDER.getParameters()
              .getVolatilityTime().subArray(1, size));
      HullWhiteOneFactorPiecewiseConstantParametersProvider provUp = HullWhiteOneFactorPiecewiseConstantParametersProvider
          .of(paramsUp, HW_PROVIDER.getDayCount(), HW_PROVIDER.getValuationDateTime());
      HullWhiteOneFactorPiecewiseConstantParametersProvider provDw = HullWhiteOneFactorPiecewiseConstantParametersProvider
          .of(paramsDw, HW_PROVIDER.getDayCount(), HW_PROVIDER.getValuationDateTime());
      double priceUp = PRICER.price(FUTURE, RATE_PROVIDER, provUp);
      double priceDw = PRICER.price(FUTURE, RATE_PROVIDER, provDw);
      expected[i] = 0.5 * (priceUp - priceDw) / TOL_FD;
    }
    assertThat(DoubleArrayMath.fuzzyEquals(computed.toArray(), expected, TOL_FD)).isTrue();
  }

  //-------------------------------------------------------------------------
  @Test
  public void regression_value() {
    double price = PRICER.price(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    assertThat(price).isCloseTo(0.9802338355115904, offset(TOL));
    double parRate = PRICER.parRate(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    assertThat(parRate).isCloseTo(0.01976616448840962, offset(TOL));
    double adjustment = PRICER.convexityAdjustment(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    assertThat(adjustment).isCloseTo(-1.3766374738599652E-4, offset(TOL));
  }

  @Test
  public void regression_sensitivity() {
    PointSensitivities point = PRICER.priceSensitivityRates(FUTURE, RATE_PROVIDER, HW_PROVIDER);
    CurrencyParameterSensitivities computed = RATE_PROVIDER.parameterSensitivity(point);
    double[] expected = new double[] {0.0, 0.0, 0.9514709785770106, -1.9399920741192112, 0.0, 0.0, 0.0, 0.0 };
    assertThat(computed.size()).isEqualTo(1);
    assertThat(DoubleArrayMath.fuzzyEquals(computed.getSensitivity(HullWhiteIborFutureDataSet.FWD3_NAME, EUR)
        .getSensitivity().toArray(), expected, TOL)).isTrue();
  }
}
