/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.provider.description.interestrate; import java.io.Serializable; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.opengamma.analytics.financial.forex.method.FXMatrix; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.instrument.index.IndexON; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.ForwardSensitivity; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; import com.opengamma.util.tuple.DoublesPair; /** * Class describing a "market" with discounting, forward, price index and credit curves. * The forward rate are computed as the ratio of discount factors stored in {@link YieldAndDiscountCurve}. */ public class MulticurveProviderDiscount implements MulticurveProviderInterface, Serializable { private static final long serialVersionUID = 1L; private static final Logger s_logger = LoggerFactory.getLogger(MulticurveProviderDiscount.class); /** * A map with one (discounting) curve by currency. */ private final Map<Currency, YieldAndDiscountCurve> _discountingCurves; /** * A map with one (forward) curve by ON index. */ private final Map<IndexON, YieldAndDiscountCurve> _forwardONCurves; /** * A map with one (forward) curve by Ibor index. */ private final Map<IborIndex, YieldAndDiscountCurve> _forwardIborCurves; /** * The matrix containing the exchange rates. */ private FXMatrix _fxMatrix; /** * Map of all curves used in the provider. */ private Map<String, YieldAndDiscountCurve> _allCurves; /** * Map of curve names to currencies. */ private ListMultimap<String, Currency> _namesToCurrency; /** * Map of curve names to ibor indices. */ private ListMultimap<String, IborIndex> _namesToIborIndex; /** * Map of curve names to overnight indices. */ private ListMultimap<String, IndexON> _namesToONIndex; /** * Constructor with empty maps for discounting, forward and price index. */ public MulticurveProviderDiscount() { // TODO: Do we need a LinkedHashMap or a more efficient Map could be used? _discountingCurves = new LinkedHashMap<>(); _forwardIborCurves = new LinkedHashMap<>(); _forwardONCurves = new LinkedHashMap<>(); _fxMatrix = new FXMatrix(); setAllCurves(); } /** * Constructor with empty maps for discounting, forward and price index. * @param fxMatrix The FXMatrix, not null */ public MulticurveProviderDiscount(final FXMatrix fxMatrix) { ArgumentChecker.notNull(fxMatrix, "FX matrix"); _discountingCurves = new LinkedHashMap<>(); _forwardIborCurves = new LinkedHashMap<>(); _forwardONCurves = new LinkedHashMap<>(); _fxMatrix = fxMatrix; setAllCurves(); } /** * Constructor from an existing market. The given market maps are used for the new market (the same maps are used, not copied). * @param discountingCurves A map with one (discounting) curve by currency, not null * @param forwardIborCurves A map with one (forward) curve by Ibor index, not null * @param forwardONCurves A map with one (forward) curve by ON index, not null * @param fxMatrix The FXMatrix, not null */ public MulticurveProviderDiscount(final Map<Currency, YieldAndDiscountCurve> discountingCurves, final Map<IborIndex, YieldAndDiscountCurve> forwardIborCurves, final Map<IndexON, YieldAndDiscountCurve> forwardONCurves, final FXMatrix fxMatrix) { ArgumentChecker.notNull(discountingCurves, "discounting curve"); ArgumentChecker.notNull(forwardIborCurves, "forward ibor curve"); ArgumentChecker.notNull(forwardONCurves, "forward overnight curve"); ArgumentChecker.notNull(fxMatrix, "FX matrix"); _discountingCurves = discountingCurves; _forwardIborCurves = forwardIborCurves; _forwardONCurves = forwardONCurves; _fxMatrix = fxMatrix; setAllCurves(); } /** * Constructor from exiting maps. The given maps are used for the new market (the same maps are used, not copied). * @param market The existing market, not null */ public MulticurveProviderDiscount(final MulticurveProviderDiscount market) { ArgumentChecker.notNull(market, "market"); _discountingCurves = market._discountingCurves; _forwardIborCurves = market._forwardIborCurves; _forwardONCurves = market._forwardONCurves; _fxMatrix = market._fxMatrix; setAllCurves(); } @Override public MulticurveProviderInterface getMulticurveProvider() { return this; } @Override public MulticurveProviderDiscount copy() { final LinkedHashMap<Currency, YieldAndDiscountCurve> discountingCurves = new LinkedHashMap<>(_discountingCurves); final LinkedHashMap<IborIndex, YieldAndDiscountCurve> forwardIborCurves = new LinkedHashMap<>(_forwardIborCurves); final LinkedHashMap<IndexON, YieldAndDiscountCurve> forwardONCurves = new LinkedHashMap<>(_forwardONCurves); final FXMatrix fxMatrix = _fxMatrix.copy(); return new MulticurveProviderDiscount(discountingCurves, forwardIborCurves, forwardONCurves, fxMatrix); } /** * Adds all curves to a single map containing (curve name, curve) elements. */ private void setAllCurves() { _allCurves = new LinkedHashMap<>(); _namesToCurrency = ArrayListMultimap.create(); _namesToIborIndex = ArrayListMultimap.create(); _namesToONIndex = ArrayListMultimap.create(); final Set<Currency> ccySet = _discountingCurves.keySet(); for (final Currency ccy : ccySet) { final String name = _discountingCurves.get(ccy).getName(); _allCurves.put(name, _discountingCurves.get(ccy)); _namesToCurrency.put(name, ccy); } final Set<IborIndex> indexSet = _forwardIborCurves.keySet(); for (final IborIndex index : indexSet) { final String name = _forwardIborCurves.get(index).getName(); _allCurves.put(name, _forwardIborCurves.get(index)); _namesToIborIndex.put(name, index); } final Set<IndexON> indexONSet = _forwardONCurves.keySet(); for (final IndexON index : indexONSet) { final String name = _forwardONCurves.get(index).getName(); _allCurves.put(name, _forwardONCurves.get(index)); _namesToONIndex.put(name, index); } } @Override public double[] parameterSensitivity(final String name, final List<DoublesPair> pointSensitivity) { final YieldAndDiscountCurve curve = _allCurves.get(name); if (curve == null) { throw new UnsupportedOperationException("Cannot get sensitivities for curve called " + name); } final int nbParameters = curve.getNumberOfParameters(); final double[] result = new double[nbParameters]; if (pointSensitivity != null && pointSensitivity.size() > 0) { for (final DoublesPair timeAndS : pointSensitivity) { final double[] sensi1Point = curve.getInterestRateParameterSensitivity(timeAndS.getFirst()); for (int loopparam = 0; loopparam < nbParameters; loopparam++) { result[loopparam] += timeAndS.getSecond() * sensi1Point[loopparam]; } } } return result; } @Override public double[] parameterForwardSensitivity(final String name, final List<ForwardSensitivity> pointSensitivity) { final YieldAndDiscountCurve curve = _allCurves.get(name); if (curve == null) { throw new UnsupportedOperationException("Cannot get sensitivities for curve called " + name); } final int nbParameters = curve.getNumberOfParameters(); final double[] result = new double[nbParameters]; if (pointSensitivity != null && pointSensitivity.size() > 0) { for (final ForwardSensitivity timeAndS : pointSensitivity) { final double startTime = timeAndS.getStartTime(); final double endTime = timeAndS.getEndTime(); final double forwardBar = timeAndS.getValue(); // Implementation note: only the sensitivity to the forward is available. The sensitivity to the pseudo-discount factors need to be computed. final double dfForwardStart = curve.getDiscountFactor(startTime); final double dfForwardEnd = curve.getDiscountFactor(endTime); final double dFwddyStart = timeAndS.derivativeToYieldStart(dfForwardStart, dfForwardEnd); final double dFwddyEnd = timeAndS.derivativeToYieldEnd(dfForwardStart, dfForwardEnd); final double[] sensiPtStart = curve.getInterestRateParameterSensitivity(startTime); final double[] sensiPtEnd = curve.getInterestRateParameterSensitivity(endTime); for (int loopparam = 0; loopparam < nbParameters; loopparam++) { result[loopparam] += dFwddyStart * sensiPtStart[loopparam] * forwardBar; result[loopparam] += dFwddyEnd * sensiPtEnd[loopparam] * forwardBar; } } } return result; } @Override public Integer getNumberOfParameters(final String name) { return _allCurves.get(name).getNumberOfParameters(); } @Override public List<String> getUnderlyingCurvesNames(final String name) { return _allCurves.get(name).getUnderlyingCurvesNames(); } /** * Gets a named curve. * @param name The name * @return The curve, null if not found */ public YieldAndDiscountCurve getCurve(final String name) { return _allCurves.get(name); } /** * Gets the currencies of a named curve. * If there is no discounting curve with this name in this provider, returns empty list. * @param name The name of a curve * @return The currencies. */ public List<Currency> getCurrencyForName(final String name) { return _namesToCurrency.get(name); } /** * Gets the Ibor indices of a named curve. * If there is no forward Ibor curve with this name in this provider, returns empty list. * @param name The name of a curve * @return The ibor indices. */ public List<IborIndex> getIborIndexForName(final String name) { return _namesToIborIndex.get(name); } /** * Gets the overnight indices of a named curve. * If there is no forward overnight curve with this name in this provider, returns empty list. * @param name The name of a curve * @return The overnight indices. */ public List<IndexON> getOvernightIndexForName(final String name) { return _namesToONIndex.get(name); } @Override public double getDiscountFactor(final Currency ccy, final Double time) { if (_discountingCurves.containsKey(ccy)) { return _discountingCurves.get(ccy).getDiscountFactor(time); } throw new IllegalArgumentException("Currency discounting curve not found: " + ccy); } @Override public String getName(final Currency ccy) { if (_discountingCurves.containsKey(ccy)) { return _discountingCurves.get(ccy).getName(); } throw new IllegalArgumentException("Currency discounting curve not found: " + ccy); } @Override public Set<Currency> getCurrencies() { return _discountingCurves.keySet(); } @Override public double getInvestmentFactor(final IborIndex index, final double startTime, final double endTime, final double accrualFactor) { if (_forwardIborCurves.containsKey(index)) { return _forwardIborCurves.get(index).getDiscountFactor(startTime) / _forwardIborCurves.get(index).getDiscountFactor(endTime); } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public double getSimplyCompoundForwardRate(final IborIndex index, final double startTime, final double endTime, final double accrualFactor) { if (_forwardIborCurves.containsKey(index)) { return (_forwardIborCurves.get(index).getDiscountFactor(startTime) / _forwardIborCurves.get(index).getDiscountFactor(endTime) - 1) / accrualFactor; } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public double getSimplyCompoundForwardRate(final IborIndex index, final double startTime, final double endTime) { ArgumentChecker.isFalse(startTime == endTime, "Start time should be different from end time"); final double accrualFactor = endTime - startTime; return getSimplyCompoundForwardRate(index, startTime, endTime, accrualFactor); } @Override public double getAnnuallyCompoundForwardRate(final IborIndex index, final double startTime, final double endTime, final double accrualFactor) { ArgumentChecker.isFalse(accrualFactor == 0.0, "The accrual factor can't be null"); if (_forwardIborCurves.containsKey(index)) { return (Math.pow(_forwardIborCurves.get(index).getDiscountFactor(startTime) / _forwardIborCurves.get(index).getDiscountFactor(endTime), 1 / accrualFactor) - 1); } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public double getAnnuallyCompoundForwardRate(final IborIndex index, final double startTime, final double endTime) { ArgumentChecker.isFalse(startTime == endTime, "Start time should be different from end time"); final double accrualFactor = endTime - startTime; return getAnnuallyCompoundForwardRate(index, startTime, endTime, accrualFactor); } @Override public String getName(final IborIndex index) { if (_forwardIborCurves.containsKey(index)) { return _forwardIborCurves.get(index).getName(); } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public Set<IborIndex> getIndexesIbor() { return _forwardIborCurves.keySet(); } @Override public double getInvestmentFactor(final IndexON index, final double startTime, final double endTime, final double accrualFactor) { if (_forwardONCurves.containsKey(index)) { return _forwardONCurves.get(index).getDiscountFactor(startTime) / _forwardONCurves.get(index).getDiscountFactor(endTime); } throw new IllegalArgumentException("Forward ON curve not found: " + index); } @Override public double getSimplyCompoundForwardRate(final IndexON index, final double startTime, final double endTime, final double accrualFactor) { if (_forwardONCurves.containsKey(index)) { return (_forwardONCurves.get(index).getDiscountFactor(startTime) / _forwardONCurves.get(index).getDiscountFactor(endTime) - 1) / accrualFactor; } throw new IllegalArgumentException("Forward ON curve not found: " + index); } @Override public double getSimplyCompoundForwardRate(final IndexON index, final double startTime, final double endTime) { ArgumentChecker.isFalse(startTime == endTime, "Start time should be different from end time"); final double accrualFactor = endTime - startTime; return getSimplyCompoundForwardRate(index, startTime, endTime, accrualFactor); } @Override public double getAnnuallyCompoundForwardRate(final IndexON index, final double startTime, final double endTime, final double accrualFactor) { ArgumentChecker.isFalse(accrualFactor == 0.0, "The accrual factor can't be null"); if (_forwardONCurves.containsKey(index)) { return Math.pow(_forwardONCurves.get(index).getDiscountFactor(startTime) / _forwardONCurves.get(index).getDiscountFactor(endTime), 1 / accrualFactor) - 1; } throw new IllegalArgumentException("Forward ON curve not found: " + index); } @Override public double getAnnuallyCompoundForwardRate(final IndexON index, final double startTime, final double endTime) { ArgumentChecker.isFalse(startTime == endTime, "Start time should be different from end time"); final double accrualFactor = endTime - startTime; return getAnnuallyCompoundForwardRate(index, startTime, endTime, accrualFactor); } @Override public String getName(final IndexON index) { if (_forwardONCurves.containsKey(index)) { return _forwardONCurves.get(index).getName(); } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public Set<IndexON> getIndexesON() { return _forwardONCurves.keySet(); } /** * Gets the discounting curve associated in a given currency in the market. * @param ccy The currency. * @return The curve. */ public YieldAndDiscountCurve getCurve(final Currency ccy) { if (_discountingCurves.containsKey(ccy)) { return _discountingCurves.get(ccy); } throw new IllegalArgumentException("Currency discounting curve not found: " + ccy); } /** * Gets the forward curve associated to a given Ibor index in the market. * @param index The Ibor index. * @return The curve. */ public YieldAndDiscountCurve getCurve(final IborIndex index) { if (_forwardIborCurves.containsKey(index)) { return _forwardIborCurves.get(index); } throw new IllegalArgumentException("Forward curve not found: " + index); } /** * Gets the forward curve associated to a given ON index in the market. * @param index The ON index. * @return The curve. */ public YieldAndDiscountCurve getCurve(final IndexON index) { if (_forwardONCurves.containsKey(index)) { return _forwardONCurves.get(index); } throw new IllegalArgumentException("Forward curve not found: " + index); } @Override public Set<String> getAllNames() { return getAllCurveNames(); } /** * Sets the discounting curve for a given currency. * @param ccy The currency. * @param curve The yield curve used for discounting. */ public void setCurve(final Currency ccy, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(ccy, "currency"); ArgumentChecker.notNull(curve, "curve"); if (!_discountingCurves.containsKey(ccy)) { _discountingCurves.put(ccy, curve); setAllCurves(); } else { boolean curvesEqual = _discountingCurves.get(ccy).equals(curve); s_logger.debug("Two {} discounting curves for {}: {} {}", curvesEqual ? "equal" : "unequal", ccy.getCode(), curve, _discountingCurves.get(ccy)); if (!curvesEqual) { throw new IllegalArgumentException("Attempting to replace " + ccy.getCode() + " discounting curve with " + "different curve. existing: " + _discountingCurves.get(ccy) + ", new: " + curve); } } } /** * Sets the curve associated to an Ibor index. * @param index The index. * @param curve The curve. */ public void setCurve(final IborIndex index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "index"); ArgumentChecker.notNull(curve, "curve"); if (!_forwardIborCurves.containsKey(index)) { _forwardIborCurves.put(index, curve); setAllCurves(); } else if (!_forwardIborCurves.get(index).equals(curve)) { throw new IllegalArgumentException("Ibor index forward curve already set: " + index.toString()); } } /** * Sets the curve associated to an ON index. * @param index The index. * @param curve The curve. */ public void setCurve(final IndexON index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "index"); ArgumentChecker.notNull(curve, "curve"); if (!_forwardONCurves.containsKey(index)) { _forwardONCurves.put(index, curve); setAllCurves(); } else if (!_forwardONCurves.get(index).equals(curve)) { throw new IllegalArgumentException("ON index forward curve already set: " + index.toString()); } } /** * Set all the curves contains in another bundle. If a currency or index is already present in the map, the associated curve is changed. * @param other The other bundle. */ //TODO: REVIEW: Should we check that the curve are already present? public void setAll(final MulticurveProviderDiscount other) { ArgumentChecker.notNull(other, "Market bundle"); _discountingCurves.putAll(other._discountingCurves); _forwardIborCurves.putAll(other._forwardIborCurves); _forwardONCurves.putAll(other._forwardONCurves); setAllCurves(); } /** * Sets the FX matrix. * @param fxMatrix The FX matrix, not null */ public void setForexMatrix(final FXMatrix fxMatrix) { ArgumentChecker.notNull(fxMatrix, "FX matrix"); _fxMatrix = fxMatrix; } /** * Replaces the discounting curve for a given currency. * @param ccy The currency. * @param curve The yield curve used for discounting. * @throws IllegalArgumentException if curve name NOT already present */ public void replaceCurve(final Currency ccy, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(ccy, "Currency"); ArgumentChecker.notNull(curve, "curve"); if (!_discountingCurves.containsKey(ccy)) { throw new IllegalArgumentException("Currency discounting curve not in set: " + ccy); } _discountingCurves.put(ccy, curve); setAllCurves(); } /** * Replaces the forward curve for a given index. * @param index The index. * @param curve The yield curve used for forward. * @throws IllegalArgumentException if curve name NOT already present */ public void replaceCurve(final IborIndex index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "Index"); ArgumentChecker.notNull(curve, "curve"); if (!_forwardIborCurves.containsKey(index)) { throw new IllegalArgumentException("Forward curve not in set: " + index); } _forwardIborCurves.put(index, curve); setAllCurves(); } /** * Replaces the forward curve for a given ON index. * @param index The index. * @param curve The yield curve used for forward. * @throws IllegalArgumentException if curve name NOT already present */ public void replaceCurve(final IndexON index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "Index"); ArgumentChecker.notNull(curve, "curve"); if (!_forwardONCurves.containsKey(index)) { throw new IllegalArgumentException("Forward curve not in set: " + index); } _forwardONCurves.put(index, curve); setAllCurves(); } /** * Sets or replaces the discounting curve for a given currency. * If the currency has not associated curve, the currency and the curve are added. * If the currency has already an associated curve, the curve for that currency is replace by the one provided. * @param ccy The currency. * @param curve The yield curve used for discounting. */ public void setOrReplaceCurve(final Currency ccy, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(ccy, "Currency"); ArgumentChecker.notNull(curve, "curve"); _discountingCurves.put(ccy, curve); setAllCurves(); } /** * Set or replaces the forward curve for a given index. * If the currency has not associated curve, the currency and the curve are added. * If the currency has already an associated curve, the curve for that currency is replace by the one provided. * @param index The index. * @param curve The yield curve used for forward. * @throws IllegalArgumentException if curve name NOT already present */ public void setOrReplaceCurve(final IborIndex index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "Index"); ArgumentChecker.notNull(curve, "curve"); _forwardIborCurves.put(index, curve); setAllCurves(); } /** * Set or replaces the forward curve for a given ON index. * If the currency has not associated curve, the currency and the curve are added. * If the currency has already an associated curve, the curve for that currency is replace by the one provided. * @param index The index. * @param curve The yield curve used for forward. * @throws IllegalArgumentException if curve name NOT already present */ public void setOrReplaceCurve(final IndexON index, final YieldAndDiscountCurve curve) { ArgumentChecker.notNull(index, "Index"); ArgumentChecker.notNull(curve, "curve"); _forwardONCurves.put(index, curve); setAllCurves(); } /** * Remove the discounting curve for a given currency. * @param ccy The currency. * @throws IllegalArgumentException if curve name NOT already present */ public void removeCurve(final Currency ccy) { ArgumentChecker.notNull(ccy, "Currency"); if (!_discountingCurves.containsKey(ccy)) { throw new IllegalArgumentException("Currency discounting curve not in set: " + ccy); } _discountingCurves.remove(ccy); setAllCurves(); } @Override public double getFxRate(final Currency ccy1, final Currency ccy2) { return _fxMatrix.getFxRate(ccy1, ccy2); } /** * Gets the underlying FXMatrix containing the exchange rates. * @return The matrix. */ @Override public FXMatrix getFxRates() { return _fxMatrix; } /** * Returns an unmodifiable copy of the currency to discounting curves map. * @return The discounting curve map */ public Map<Currency, YieldAndDiscountCurve> getDiscountingCurves() { return Collections.unmodifiableMap(_discountingCurves); } /** * Returns an unmodifiable copy of the ibor index to forward curves map. * @return The forward ibor curve map */ public Map<IborIndex, YieldAndDiscountCurve> getForwardIborCurves() { return Collections.unmodifiableMap(_forwardIborCurves); } /** * Returns an unmodifiable copy of the overnight index to forward curves map. * @return The forward overnight curve map */ public Map<IndexON, YieldAndDiscountCurve> getForwardONCurves() { return Collections.unmodifiableMap(_forwardONCurves); } @Override public Set<String> getAllCurveNames() { return Collections.unmodifiableSortedSet(new TreeSet<>(_allCurves.keySet())); } @Override public String toString() { return _allCurves.keySet().toString(); } /** * Returns a new provider with the discounting curve for a particular currency replaced. * @param ccy The currency, not null * @param replacement The replacement discounting curve, not null * @return A new provider with the discounting curve for the currency replaced by the input curve. */ public MulticurveProviderDiscount withDiscountFactor(final Currency ccy, final YieldAndDiscountCurve replacement) { ArgumentChecker.notNull(ccy, "currency"); ArgumentChecker.notNull(replacement, "replacement"); // REVIEW: Is this too slow for the pricing of cash-flows? final Map<Currency, YieldAndDiscountCurve> newDiscountCurves = new LinkedHashMap<>(_discountingCurves); newDiscountCurves.put(ccy, replacement); //TODO think about ccy not existing in current map final MulticurveProviderDiscount decorated = new MulticurveProviderDiscount(newDiscountCurves, _forwardIborCurves, _forwardONCurves, _fxMatrix); return decorated; } /** * Returns a new provider with the curve for a particular ibor index replaced. * @param index The ibor index, not null * @param replacement The replacement ibor index curve, not null * @return A new provider with the ibor index curve replaced by the input curve. */ public MulticurveProviderDiscount withForward(final IborIndex index, final YieldAndDiscountCurve replacement) { ArgumentChecker.notNull(index, "index"); ArgumentChecker.notNull(replacement, "replacement"); final Map<IborIndex, YieldAndDiscountCurve> newForwardCurves = new LinkedHashMap<>(_forwardIborCurves); newForwardCurves.put(index, replacement); final MulticurveProviderDiscount decorated = new MulticurveProviderDiscount(_discountingCurves, newForwardCurves, _forwardONCurves, _fxMatrix); return decorated; } /** * Returns a new provider with the curve for a particular overnight index replaced. * @param index The overnight index, not null * @param replacement The replacement overnight index curve, not null * @return A new provider with the overnight index curve replaced by the input curve. */ public MulticurveProviderDiscount withForward(final IndexON index, final YieldAndDiscountCurve replacement) { ArgumentChecker.notNull(index, "index"); ArgumentChecker.notNull(replacement, "replacement"); final Map<IndexON, YieldAndDiscountCurve> newForwardCurves = new LinkedHashMap<>(_forwardONCurves); newForwardCurves.put(index, replacement); final MulticurveProviderDiscount decorated = new MulticurveProviderDiscount(_discountingCurves, _forwardIborCurves, newForwardCurves, _fxMatrix); return decorated; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + _discountingCurves.hashCode(); result = prime * result + _forwardIborCurves.hashCode(); result = prime * result + _forwardONCurves.hashCode(); result = prime * result + _fxMatrix.hashCode(); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof MulticurveProviderDiscount)) { return false; } final MulticurveProviderDiscount other = (MulticurveProviderDiscount) obj; if (!ObjectUtils.equals(_discountingCurves, other._discountingCurves)) { return false; } if (!ObjectUtils.equals(_forwardIborCurves, other._forwardIborCurves)) { return false; } if (!ObjectUtils.equals(_forwardONCurves, other._forwardONCurves)) { return false; } if (!ObjectUtils.equals(_fxMatrix, other._fxMatrix)) { return false; } return true; } }