/**
 * For an instrument, this calculates the sensitivity of the present value (PV) to points on the
 * yield curve(s) (i.e. dPV/dR at every point the instrument has sensitivity). The return format is
 * a map with curve names (String) as keys and List of DoublesPair as the values; each list holds
 * set of time (corresponding to point of the yield curve) and sensitivity pairs (i.e. dPV/dR at
 * that time). <b>Note:</b> The length of the list is instrument dependent and may have repeated
 * times (with the understanding the sensitivities should be summed).
 */
public class PresentValueCurveSensitivityCalculator
    extends AbstractInstrumentDerivativeVisitor<YieldCurveBundle, Map<String, List<DoublesPair>>> {
  // TODO: Change the output format from Map to InterestRateCurveSensitivity, which wraps the map
  // and adds common functionality.

  /** The method unique instance. */
  private static final PresentValueCurveSensitivityCalculator INSTANCE =
      new PresentValueCurveSensitivityCalculator();

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

  /** Constructor. */
  PresentValueCurveSensitivityCalculator() {}

  /** The method used for OIS coupons. */
  private static final CouponOISDiscountingMethod METHOD_OIS =
      CouponOISDiscountingMethod.getInstance();

  private static final CouponIborDiscountingMethod METHOD_IBOR =
      CouponIborDiscountingMethod.getInstance();
  private static final CouponIborGearingDiscountingMethod METHOD_IBOR_GEARING =
      CouponIborGearingDiscountingMethod.getInstance();
  private static final CashDiscountingMethod METHOD_DEPOSIT = CashDiscountingMethod.getInstance();
  private static final DepositZeroDiscountingMethod METHOD_DEPOSIT_ZERO =
      DepositZeroDiscountingMethod.getInstance();
  private static final BillSecurityDiscountingMethod METHOD_BILL_SECURITY =
      BillSecurityDiscountingMethod.getInstance();
  private static final BillTransactionDiscountingMethod METHOD_BILL_TRANSACTION =
      BillTransactionDiscountingMethod.getInstance();

  @Override
  public Map<String, List<DoublesPair>> visit(
      final InstrumentDerivative instrument, final YieldCurveBundle curves) {
    return instrument.accept(this, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCash(final Cash cash, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT.presentValueCurveSensitivity(cash, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitDepositZero(
      final DepositZero deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT_ZERO.presentValueCurveSensitivity(deposit, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitForwardRateAgreement(
      final ForwardRateAgreement fra, final YieldCurveBundle curves) {
    final ForwardRateAgreementDiscountingMethod method =
        ForwardRateAgreementDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(fra, curves).getSensitivities();
  }

  /** {@inheritDoc} Future transaction pricing without convexity adjustment. */
  @Override
  public Map<String, List<DoublesPair>> visitInterestRateFuture(
      final InterestRateFuture future, final YieldCurveBundle curves) {
    final InterestRateFutureDiscountingMethod method =
        InterestRateFutureDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(future, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFixedSecurity(
      final BondFixedSecurity bond, final YieldCurveBundle curves) {
    final BondSecurityDiscountingMethod method = BondSecurityDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFixedTransaction(
      final BondFixedTransaction bond, final YieldCurveBundle curves) {
    final BondTransactionDiscountingMethod method = BondTransactionDiscountingMethod.getInstance();
    return method.presentValueSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondIborTransaction(
      final BondIborTransaction bond, final YieldCurveBundle curves) {
    final BondTransactionDiscountingMethod method = BondTransactionDiscountingMethod.getInstance();
    return method.presentValueSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBillSecurity(
      final BillSecurity bill, final YieldCurveBundle curves) {
    return METHOD_BILL_SECURITY.presentValueCurveSensitivity(bill, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBillTransaction(
      final BillTransaction bill, final YieldCurveBundle curves) {
    return METHOD_BILL_TRANSACTION.presentValueCurveSensitivity(bill, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFuture(
      final BondFuture bondFuture, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(bondFuture);
    final BondFutureDiscountingMethod method = BondFutureDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(bondFuture, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitSwap(
      final Swap<?, ?> swap, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseR = visit(swap.getSecondLeg(), curves);
    final Map<String, List<DoublesPair>> senseP = visit(swap.getFirstLeg(), curves);
    return addSensitivity(senseR, senseP);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedCouponSwap(
      final SwapFixedCoupon<?> swap, final YieldCurveBundle curves) {
    return visitSwap(swap, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitTenorSwap(
      final TenorSwap<? extends Payment> swap, final YieldCurveBundle curves) {
    return visitSwap(swap, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFloatingRateNote(
      final FloatingRateNote frn, final YieldCurveBundle curves) {
    return visitSwap(frn, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCrossCurrencySwap(
      final CrossCurrencySwap ccs, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseD = visit(ccs.getDomesticLeg(), curves);
    final Map<String, List<DoublesPair>> senseF = visit(ccs.getForeignLeg(), curves);
    // Note the sensitivities subtract rather than add here because the CCS is set up as domestic
    // FRN minus a foreign FRN
    return addSensitivity(senseD, multiplySensitivity(senseF, -ccs.getSpotFX()));
  }

  @Override
  public Map<String, List<DoublesPair>> visitForexForward(
      final ForexForward fx, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseP1 = visit(fx.getPaymentCurrency1(), curves);
    final Map<String, List<DoublesPair>> senseP2 = visit(fx.getPaymentCurrency2(), curves);
    // Note the sensitivities add rather than subtract here because the FX Forward is set up as a
    // notional in one currency PLUS a notional in another  with the  opposite sign
    return InterestRateCurveSensitivityUtils.addSensitivity(
        senseP1, multiplySensitivity(senseP2, fx.getSpotForexRate()));
  }

  @Override
  public Map<String, List<DoublesPair>> visitGenericAnnuity(
      final Annuity<? extends Payment> annuity, final YieldCurveBundle data) {
    final Map<String, List<DoublesPair>> map = new HashMap<String, List<DoublesPair>>();
    for (final Payment p : annuity.getPayments()) {
      final Map<String, List<DoublesPair>> tempMap = visit(p, data);
      for (final String name : tempMap.keySet()) {
        if (!map.containsKey(name)) {
          map.put(name, tempMap.get(name));
        } else {
          final List<DoublesPair> tempList = map.get(name);
          tempList.addAll(tempMap.get(name));
          map.put(name, tempList);
        }
      }
    }
    return map;
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedPayment(
      final PaymentFixed payment, final YieldCurveBundle data) {
    final String curveName = payment.getFundingCurveName();
    final YieldAndDiscountCurve curve = data.getCurve(curveName);
    final double t = payment.getPaymentTime();

    final DoublesPair s = new DoublesPair(t, -t * payment.getAmount() * curve.getDiscountFactor(t));
    final List<DoublesPair> list = new ArrayList<DoublesPair>();
    list.add(s);
    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();
    result.put(curveName, list);
    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIborSpread(
      final CouponIborSpread payment, final YieldCurveBundle data) {
    final String fundingCurveName = payment.getFundingCurveName();
    final String liborCurveName = payment.getForwardCurveName();
    final YieldAndDiscountCurve fundCurve = data.getCurve(fundingCurveName);
    final YieldAndDiscountCurve liborCurve = data.getCurve(liborCurveName);

    final double tPay = payment.getPaymentTime();
    final double tStart = payment.getFixingPeriodStartTime();
    final double tEnd = payment.getFixingPeriodEndTime();
    final double dfPay = fundCurve.getDiscountFactor(tPay);
    final double dfStart = liborCurve.getDiscountFactor(tStart);
    final double dfEnd = liborCurve.getDiscountFactor(tEnd);
    final double forward = (dfStart / dfEnd - 1) / payment.getFixingYearFraction();
    final double notional = payment.getNotional();

    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();

    List<DoublesPair> temp = new ArrayList<DoublesPair>();
    DoublesPair s;
    s =
        new DoublesPair(
            tPay,
            -tPay
                * dfPay
                * notional
                * (forward + payment.getSpread())
                * payment.getPaymentYearFraction());
    temp.add(s);

    if (!liborCurveName.equals(fundingCurveName)) {
      result.put(fundingCurveName, temp);
      temp = new ArrayList<DoublesPair>();
    }

    final double ratio =
        notional
            * dfPay
            * dfStart
            / dfEnd
            * payment.getPaymentYearFraction()
            / payment.getFixingYearFraction();
    s = new DoublesPair(tStart, -tStart * ratio);
    temp.add(s);
    s = new DoublesPair(tEnd, tEnd * ratio);
    temp.add(s);

    result.put(liborCurveName, temp);

    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponOIS(
      final CouponOIS payment, final YieldCurveBundle data) {
    final Map<String, List<DoublesPair>> result =
        METHOD_OIS.presentValueCurveSensitivity(payment, data).getSensitivities();
    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedCouponAnnuity(
      final AnnuityCouponFixed annuity, final YieldCurveBundle data) {
    return visitGenericAnnuity(annuity, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponFixed(
      final CouponFixed payment, final YieldCurveBundle data) {
    return visitFixedPayment(payment.toPaymentFixed(), data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitForwardLiborAnnuity(
      final AnnuityCouponIbor annuity, final YieldCurveBundle data) {
    return visitGenericAnnuity(annuity, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedFloatSwap(
      final FixedFloatSwap swap, final YieldCurveBundle data) {
    return visitFixedCouponSwap(swap, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponCMS(
      final CouponCMS payment, final YieldCurveBundle data) {
    final CouponCMSDiscountingMethod method = CouponCMSDiscountingMethod.getInstance();
    return method.presentValueSensitivity(payment, data).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIbor(
      final CouponIbor coupon, final YieldCurveBundle curves) {
    return METHOD_IBOR.presentValueCurveSensitivity(coupon, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIborGearing(
      final CouponIborGearing coupon, final YieldCurveBundle curves) {
    return METHOD_IBOR_GEARING.presentValueCurveSensitivity(coupon, curves).getSensitivities();
  }

  /**
   * Compute the sensitivity of the discount factor at a given time.
   *
   * @param curveName The curve name associated to the discount factor.
   * @param curve The curve from which the discount factor should be computed.
   * @param time The time
   * @return The sensitivity.
   */
  public static Map<String, List<DoublesPair>> discountFactorSensitivity(
      final String curveName, final YieldAndDiscountCurve curve, final double time) {
    final DoublesPair s = new DoublesPair(time, -time * curve.getDiscountFactor(time));
    final List<DoublesPair> list = new ArrayList<DoublesPair>();
    list.add(s);
    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();
    result.put(curveName, list);
    return result;
  }
}
 @Override
 public Map<String, List<DoublesPair>> visitCash(final Cash cash, final YieldCurveBundle curves) {
   return METHOD_DEPOSIT.presentValueCurveSensitivity(cash, curves).getSensitivities();
 }
/**
 * Compute the spread to be added to the rate of the instrument for which the present value of the
 * instrument is zero. The "rate" can be a "rate" or a "yield" and will depend of each instrument.
 *
 * @deprecated {@link YieldCurveBundle} is deprecated
 */
@Deprecated
public final class ParSpreadRateCalculator
    extends InstrumentDerivativeVisitorAdapter<YieldCurveBundle, Double> {

  /** The unique instance of the calculator. */
  private static final ParSpreadRateCalculator INSTANCE = new ParSpreadRateCalculator();

  /**
   * Gets the calculator instance.
   *
   * @return The calculator.
   */
  public static ParSpreadRateCalculator getInstance() {
    return INSTANCE;
  }

  /** Constructor. */
  private ParSpreadRateCalculator() {}

  /** The methods and calculators. */
  private static final PresentValueMCACalculator PVMCC = PresentValueMCACalculator.getInstance();

  private static final PresentValueBasisPointCalculator PVBPC =
      PresentValueBasisPointCalculator.getInstance();
  private static final CashDiscountingMethod METHOD_DEPOSIT = CashDiscountingMethod.getInstance();
  private static final DepositZeroDiscountingMethod METHOD_DEPOSIT_ZERO =
      DepositZeroDiscountingMethod.getInstance();
  private static final ForwardRateAgreementDiscountingMethod METHOD_FRA =
      ForwardRateAgreementDiscountingMethod.getInstance();
  //  private static final InterestRateFutureTransactionDiscountingMethod
  // METHOD_IR_FUTURES_TRANSACTION = InterestRateFutureTransactionDiscountingMethod.getInstance();
  private static final InterestRateFutureSecurityDiscountingMethod METHOD_IR_FUTURES_SECURITY =
      InterestRateFutureSecurityDiscountingMethod.getInstance();

  @Override
  public Double visitCash(final Cash deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT.parSpread(deposit, curves);
  }

  @Override
  public Double visitDepositZero(final DepositZero deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT_ZERO.parSpread(deposit, curves);
  }

  /**
   * For swaps the ParSpread is the spread to be added on each coupon of the first leg to obtain a
   * present value of zero. It is computed as the opposite of the present value of the swap divided
   * by the present value of a basis point of the first leg (as computed by the
   * PresentValueBasisPointCalculator).
   *
   * @param swap The swap.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitSwap(final Swap<?, ?> swap, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(swap);
    return -curves
            .getFxRates()
            .convert(swap.accept(PVMCC, curves), swap.getFirstLeg().getCurrency())
            .getAmount()
        / swap.getFirstLeg().accept(PVBPC, curves);
  }

  @Override
  public Double visitFixedCouponSwap(final SwapFixedCoupon<?> swap, final YieldCurveBundle curves) {
    return visitSwap(swap, curves);
  }

  /**
   * For ForwardRateAgreement the ParSpread is the spread to be added to the fixed rate to obtain a
   * present value of zero.
   *
   * @param fra The forward rate agreement.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitForwardRateAgreement(
      final ForwardRateAgreement fra, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(fra);
    return METHOD_FRA.parSpread(fra, curves);
  }

  /**
   * For InterestRateFutures the ParSpread is the spread to be added to the reference price to
   * obtain a present value of zero.
   *
   * @param future The futures.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitInterestRateFutureTransaction(
      final InterestRateFutureTransaction future, final YieldCurveBundle curves) {
    return -(METHOD_IR_FUTURES_SECURITY.price(future.getUnderlying(), curves)
        - future.getReferencePrice());
  }

  //  /**
  //   * For InterestRateFutures the ParSpread is the spread to be added to the reference price to
  // obtain a present value of zero.
  //   * @param future The futures.
  //   * @param curves The yield curve bundle.
  //   * @return The par spread.
  //   */
  //  @Override
  //  public Double visitInterestRateFutureSecurity(final InterestRateFutureSecurity future, final
  // YieldCurveBundle curves) {
  //    return -(METHOD_IR_FUTURES_SECURITY.presentValue(future, curves).getAmount());
  //  }
}
 @Override
 public Double visitCash(final Cash deposit, final YieldCurveBundle curves) {
   return METHOD_DEPOSIT.parSpread(deposit, curves);
 }
/**
 * Constructs a single yield curve and its Jacobian from an FX-implied yield curve calculation
 * configuration and a yield curve definition that contains <b>only</b> {@link
 * StripInstrumentType#CASH} strips. The transformation of the yield curve allows risk to be
 * displayed with respect to implied deposit rates, not FX forwards.
 */
public class ImpliedDepositCurveFunction extends AbstractFunction {
  /** The calculation method property value */
  public static final String IMPLIED_DEPOSIT = "ImpliedDeposit";
  /** The Cash instrument method */
  private static final CashDiscountingMethod METHOD_CASH = CashDiscountingMethod.getInstance();
  /** Calculates the par rate */
  private static final ParRateCalculator PAR_RATE_CALCULATOR = ParRateCalculator.getInstance();
  /** Calculates the sensitivity of the par rate to the curves */
  private static final ParRateCurveSensitivityCalculator PAR_RATE_SENSITIVITY_CALCULATOR =
      ParRateCurveSensitivityCalculator.getInstance();
  /** The business day convention used for FX forward dates computation * */
  private static final BusinessDayConvention MOD_FOL = BusinessDayConventions.MODIFIED_FOLLOWING;
  /** The logger */
  private static final Logger s_logger = LoggerFactory.getLogger(ImpliedDepositCurveFunction.class);
  /** The curve name */
  private final String _curveCalculationConfig;

  private ConfigSourceQuery<MultiCurveCalculationConfig> _multiCurveCalculationConfig;
  private ConfigSourceQuery<YieldCurveDefinition> _yieldCurveDefinition;

  /** @param curveCalculationConfig The curve name, not null */
  public ImpliedDepositCurveFunction(final String curveCalculationConfig) {
    ArgumentChecker.notNull(curveCalculationConfig, "curve name");
    _curveCalculationConfig = curveCalculationConfig;
  }

  @Override
  public void init(final FunctionCompilationContext context) {
    _multiCurveCalculationConfig =
        ConfigSourceQuery.init(context, this, MultiCurveCalculationConfig.class);
    _yieldCurveDefinition = ConfigSourceQuery.init(context, this, YieldCurveDefinition.class);
  }

  @Override
  public CompiledFunctionDefinition compile(
      final FunctionCompilationContext context, final Instant atInstant) {
    final MultiCurveCalculationConfig impliedConfiguration =
        _multiCurveCalculationConfig.get(_curveCalculationConfig);
    if (impliedConfiguration == null) {
      throw new OpenGammaRuntimeException(
          "Multi-curve calculation called " + _curveCalculationConfig + " was null");
    }
    ComputationTarget target =
        context.getComputationTargetResolver().resolve(impliedConfiguration.getTarget());
    if (!(target.getValue() instanceof Currency)) {
      throw new OpenGammaRuntimeException(
          "Target of curve calculation configuration was not a currency");
    }
    final Currency impliedCurrency = (Currency) target.getValue();
    if (!IMPLIED_DEPOSIT.equals(impliedConfiguration.getCalculationMethod())) {
      throw new OpenGammaRuntimeException(
          "Curve calculation method was not "
              + IMPLIED_DEPOSIT
              + " for configuration called "
              + _curveCalculationConfig);
    }
    final String[] impliedCurveNames = impliedConfiguration.getYieldCurveNames();
    if (impliedCurveNames.length != 1) {
      throw new OpenGammaRuntimeException(
          "Can only handle configurations with a single implied curve");
    }
    final LinkedHashMap<String, String[]> originalConfigurationName =
        impliedConfiguration.getExogenousConfigData();
    if (originalConfigurationName == null || originalConfigurationName.size() != 1) {
      throw new OpenGammaRuntimeException("Need a configuration with one exogenous configuration");
    }
    final Map.Entry<String, String[]> entry =
        Iterables.getOnlyElement(originalConfigurationName.entrySet());
    final String[] originalCurveNames = entry.getValue();
    if (originalCurveNames.length != 1) {
      s_logger.warn("Found more than one exogenous configuration name; using only the first");
    }
    final MultiCurveCalculationConfig originalConfiguration =
        _multiCurveCalculationConfig.get(entry.getKey());
    if (originalConfiguration == null) {
      throw new OpenGammaRuntimeException(
          "Multi-curve calculation called " + entry.getKey() + " was null");
    }
    target = context.getComputationTargetResolver().resolve(originalConfiguration.getTarget());
    if (!(target.getValue() instanceof Currency)) {
      throw new OpenGammaRuntimeException(
          "Target of curve calculation configuration was not a currency");
    }
    final Currency originalCurrency = (Currency) target.getValue();
    if (!originalCurrency.equals(impliedCurrency)) {
      throw new OpenGammaRuntimeException(
          "Currency targets for configurations "
              + _curveCalculationConfig
              + " and "
              + entry.getKey()
              + " did not match");
    }
    final YieldCurveDefinition impliedDefinition =
        _yieldCurveDefinition.get(impliedCurveNames[0] + "_" + impliedCurrency.getCode());
    if (impliedDefinition == null) {
      throw new OpenGammaRuntimeException(
          "Could not get implied definition called "
              + impliedCurveNames[0]
              + "_"
              + impliedCurrency.getCode());
    }
    final Set<FixedIncomeStrip> strips = impliedDefinition.getStrips();
    for (final FixedIncomeStrip strip : strips) {
      if (strip.getInstrumentType() != StripInstrumentType.CASH) {
        throw new OpenGammaRuntimeException(
            "Can only handle yield curve definitions with CASH strips");
      }
    }
    final ZonedDateTime atZDT = ZonedDateTime.ofInstant(atInstant, ZoneOffset.UTC);
    return new MyCompiledFunction(
        atZDT.with(LocalTime.MIDNIGHT),
        atZDT.plusDays(1).with(LocalTime.MIDNIGHT).minusNanos(1000000),
        impliedConfiguration,
        impliedDefinition,
        originalConfiguration,
        originalCurveNames[0]);
  };

  private class MyCompiledFunction extends AbstractInvokingCompiledFunction {
    /** The definition of the implied curve */
    private final YieldCurveDefinition _impliedDefinition;
    /** The implied curve calculation configuration */
    private final MultiCurveCalculationConfig _impliedConfiguration;
    /** The original curve calculation configuration */
    private final MultiCurveCalculationConfig _originalConfiguration;
    /** The implied curve name */
    private final String _impliedCurveName;
    /** The original curve name */
    private final String _originalCurveName;
    /** The currency */
    private final Currency _currency;
    /** The interpolator */
    private final String _interpolatorName;
    /** The left extrapolator */
    private final String _leftExtrapolatorName;
    /** The right extrapolator */
    private final String _rightExtrapolatorName;

    public MyCompiledFunction(
        final ZonedDateTime earliestInvokation,
        final ZonedDateTime latestInvokation,
        final MultiCurveCalculationConfig impliedConfiguration,
        final YieldCurveDefinition impliedDefinition,
        final MultiCurveCalculationConfig originalConfiguration,
        final String originalCurveName) {
      super(earliestInvokation, latestInvokation);
      _impliedConfiguration = impliedConfiguration;
      _impliedDefinition = impliedDefinition;
      _originalConfiguration = originalConfiguration;
      _impliedCurveName = impliedDefinition.getName();
      _originalCurveName = originalCurveName;
      _currency = impliedDefinition.getCurrency();
      _interpolatorName = impliedDefinition.getInterpolatorName();
      _leftExtrapolatorName = impliedDefinition.getLeftExtrapolatorName();
      _rightExtrapolatorName = impliedDefinition.getRightExtrapolatorName();
    }

    @Override
    public Set<ComputedValue> execute(
        final FunctionExecutionContext executionContext,
        final FunctionInputs inputs,
        final ComputationTarget target,
        final Set<ValueRequirement> desiredValues)
        throws AsynchronousExecution {
      final Object originalCurveObject = inputs.getValue(YIELD_CURVE);
      if (originalCurveObject == null) {
        throw new OpenGammaRuntimeException("Could not get original curve");
      }
      ValueProperties resultCurveProperties = null;
      String absoluteToleranceName = null;
      String relativeToleranceName = null;
      String iterationsName = null;
      String decompositionName = null;
      String useFiniteDifferenceName = null;
      for (final ValueRequirement desiredValue : desiredValues) {
        if (desiredValue.getValueName().equals(YIELD_CURVE)) {
          absoluteToleranceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE);
          relativeToleranceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE);
          iterationsName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_MAX_ITERATIONS);
          decompositionName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_DECOMPOSITION);
          useFiniteDifferenceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_USE_FINITE_DIFFERENCE);
          resultCurveProperties = desiredValue.getConstraints().copy().get();
          break;
        }
      }
      if (resultCurveProperties == null) {
        throw new OpenGammaRuntimeException("Could not get result curve properties");
      }
      final ValueProperties resultJacobianProperties = resultCurveProperties.withoutAny(CURVE);
      ZonedDateTime valuationDateTime =
          executionContext
              .getValuationTime()
              .atZone(executionContext.getValuationClock().getZone());
      final HolidaySource holidaySource =
          OpenGammaExecutionContext.getHolidaySource(executionContext);
      final ConventionSource conventionSource =
          OpenGammaExecutionContext.getConventionSource(executionContext);
      final Calendar calendar = CalendarUtils.getCalendar(holidaySource, _currency);
      final DepositConvention convention =
          conventionSource.getSingle(
              ExternalId.of(SCHEME_NAME, getConventionName(_currency, DEPOSIT)),
              DepositConvention.class);
      final int spotLag = convention.getSettlementDays();
      final ExternalId conventionSettlementRegion = convention.getRegionCalendar();
      ZonedDateTime spotDate;
      if (spotLag == 0 && conventionSettlementRegion == null) {
        spotDate = valuationDateTime;
      } else {
        spotDate = ScheduleCalculator.getAdjustedDate(valuationDateTime, spotLag, calendar);
        ;
      }
      final YieldCurveBundle curves = new YieldCurveBundle();
      final String fullYieldCurveName = _originalCurveName + "_" + _currency;
      curves.setCurve(fullYieldCurveName, (YieldAndDiscountCurve) originalCurveObject);
      final int n = _impliedDefinition.getStrips().size();
      final double[] t = new double[n];
      final double[] r = new double[n];
      int i = 0;
      final DayCount dayCount =
          DayCountFactory.INSTANCE.getDayCount(
              "Act/360"); // TODO: Get the convention from the curve.

      final String impliedDepositCurveName = _curveCalculationConfig + "_" + _currency.getCode();
      final List<InstrumentDerivative> derivatives = new ArrayList<>();

      for (final FixedIncomeStrip strip : _impliedDefinition.getStrips()) {
        final Tenor tenor = strip.getCurveNodePointTime();
        final ZonedDateTime paymentDate =
            ScheduleCalculator.getAdjustedDate(
                spotDate, tenor.getPeriod(), MOD_FOL, calendar, true);
        final double startTime = TimeCalculator.getTimeBetween(valuationDateTime, spotDate);
        final double endTime = TimeCalculator.getTimeBetween(valuationDateTime, paymentDate);
        final double accrualFactor = dayCount.getDayCountFraction(spotDate, paymentDate, calendar);
        final Cash cashFXCurve =
            new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, fullYieldCurveName);
        final double parRate = METHOD_CASH.parRate(cashFXCurve, curves);
        final Cash cashDepositCurve =
            new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, impliedDepositCurveName);
        derivatives.add(cashDepositCurve);
        t[i] = endTime;
        r[i++] = parRate;
      }
      final CombinedInterpolatorExtrapolator interpolator =
          CombinedInterpolatorExtrapolatorFactory.getInterpolator(
              _interpolatorName, _leftExtrapolatorName, _rightExtrapolatorName);
      final double absoluteTolerance = Double.parseDouble(absoluteToleranceName);
      final double relativeTolerance = Double.parseDouble(relativeToleranceName);
      final int iterations = Integer.parseInt(iterationsName);
      final Decomposition<?> decomposition =
          DecompositionFactory.getDecomposition(decompositionName);
      final boolean useFiniteDifference = Boolean.parseBoolean(useFiniteDifferenceName);
      final LinkedHashMap<String, double[]> curveNodes = new LinkedHashMap<>();
      final LinkedHashMap<String, Interpolator1D> interpolators = new LinkedHashMap<>();
      curveNodes.put(impliedDepositCurveName, t);
      interpolators.put(impliedDepositCurveName, interpolator);
      final FXMatrix fxMatrix = new FXMatrix();
      final YieldCurveBundle knownCurve = new YieldCurveBundle();
      final MultipleYieldCurveFinderDataBundle data =
          new MultipleYieldCurveFinderDataBundle(
              derivatives, r, knownCurve, curveNodes, interpolators, useFiniteDifference, fxMatrix);
      final NewtonVectorRootFinder rootFinder =
          new BroydenVectorRootFinder(
              absoluteTolerance, relativeTolerance, iterations, decomposition);
      final Function1D<DoubleMatrix1D, DoubleMatrix1D> curveCalculator =
          new MultipleYieldCurveFinderFunction(data, PAR_RATE_CALCULATOR);
      final Function1D<DoubleMatrix1D, DoubleMatrix2D> jacobianCalculator =
          new MultipleYieldCurveFinderJacobian(data, PAR_RATE_SENSITIVITY_CALCULATOR);
      final double[] fittedYields =
          rootFinder.getRoot(curveCalculator, jacobianCalculator, new DoubleMatrix1D(r)).getData();
      final DoubleMatrix2D jacobianMatrix =
          jacobianCalculator.evaluate(new DoubleMatrix1D(fittedYields));
      final YieldCurve impliedDepositCurve =
          new YieldCurve(
              impliedDepositCurveName,
              InterpolatedDoublesCurve.from(t, fittedYields, interpolator));
      final ValueSpecification curveSpec =
          new ValueSpecification(YIELD_CURVE, target.toSpecification(), resultCurveProperties);
      final ValueSpecification jacobianSpec =
          new ValueSpecification(
              YIELD_CURVE_JACOBIAN, target.toSpecification(), resultJacobianProperties);
      return Sets.newHashSet(
          new ComputedValue(curveSpec, impliedDepositCurve),
          new ComputedValue(jacobianSpec, jacobianMatrix));
    }

    @Override
    public ComputationTargetType getTargetType() {
      return ComputationTargetType.CURRENCY;
    }

    @Override
    public Set<ValueSpecification> getResults(
        final FunctionCompilationContext compilationContext, final ComputationTarget target) {
      final ValueProperties curveProperties =
          getCurveProperties(_impliedCurveName, _impliedConfiguration.getCalculationConfigName());
      final ValueProperties jacobianProperties =
          getJacobianProperties(_impliedConfiguration.getCalculationConfigName());
      final ComputationTargetSpecification targetSpec = target.toSpecification();
      final ValueSpecification curve =
          new ValueSpecification(YIELD_CURVE, targetSpec, curveProperties);
      final ValueSpecification jacobian =
          new ValueSpecification(YIELD_CURVE_JACOBIAN, targetSpec, jacobianProperties);
      return Sets.newHashSet(curve, jacobian);
    }

    @Override
    public Set<ValueRequirement> getRequirements(
        final FunctionCompilationContext compilationContext,
        final ComputationTarget target,
        final ValueRequirement desiredValue) {
      final ValueProperties constraints = desiredValue.getConstraints();
      final Set<String> rootFinderAbsoluteTolerance =
          constraints.getValues(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE);
      if (rootFinderAbsoluteTolerance == null || rootFinderAbsoluteTolerance.size() != 1) {
        return null;
      }
      final Set<String> rootFinderRelativeTolerance =
          constraints.getValues(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE);
      if (rootFinderRelativeTolerance == null || rootFinderRelativeTolerance.size() != 1) {
        return null;
      }
      final Set<String> maxIterations = constraints.getValues(PROPERTY_ROOT_FINDER_MAX_ITERATIONS);
      if (maxIterations == null || maxIterations.size() != 1) {
        return null;
      }
      final Set<String> decomposition = constraints.getValues(PROPERTY_DECOMPOSITION);
      if (decomposition == null || decomposition.size() != 1) {
        return null;
      }
      final Set<String> useFiniteDifference = constraints.getValues(PROPERTY_USE_FINITE_DIFFERENCE);
      if (useFiniteDifference == null || useFiniteDifference.size() != 1) {
        return null;
      }
      if (!_originalConfiguration.getTarget().equals(target.toSpecification())) {
        s_logger.info(
            "Invalid target, was {} - expected {}", target, _originalConfiguration.getTarget());
        return null;
      }
      final ValueProperties properties =
          ValueProperties.builder()
              .with(CURVE_CALCULATION_METHOD, _originalConfiguration.getCalculationMethod())
              .with(CURVE_CALCULATION_CONFIG, _originalConfiguration.getCalculationConfigName())
              .with(CURVE, _originalCurveName)
              .get();
      return Collections.singleton(
          new ValueRequirement(YIELD_CURVE, target.toSpecification(), properties));
    }

    /**
     * Gets the properties of the implied yield curve.
     *
     * @param curveName The implied curve name
     * @return The properties
     */
    private ValueProperties getCurveProperties(
        final String curveName, final String curveCalculationConfig) {
      return createValueProperties()
          .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT)
          .with(CURVE, curveName)
          .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig)
          .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS)
          .withAny(PROPERTY_DECOMPOSITION)
          .withAny(PROPERTY_USE_FINITE_DIFFERENCE)
          .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName)
          .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName)
          .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName)
          .get();
    }

    /**
     * Gets the properties of the Jacobian with no values set.
     *
     * @return The properties.
     */
    private ValueProperties getJacobianProperties(final String curveCalculationConfig) {
      return createValueProperties()
          .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT)
          .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig)
          .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS)
          .withAny(PROPERTY_DECOMPOSITION)
          .withAny(PROPERTY_USE_FINITE_DIFFERENCE)
          .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName)
          .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName)
          .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName)
          .get();
    }
  }
}