/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame;
import static com.opengamma.util.result.FailureStatus.ERROR;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.LinkedListMultimap;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.curve.interestrate.generator.GeneratorCurveYieldInterpolated;
import com.opengamma.analytics.financial.curve.interestrate.generator.GeneratorCurveYieldInterpolatedAnchorNode;
import com.opengamma.analytics.financial.curve.interestrate.generator.GeneratorYDCurve;
import com.opengamma.analytics.financial.forex.method.FXMatrix;
import com.opengamma.analytics.financial.instrument.InstrumentDefinition;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.analytics.financial.instrument.index.IndexON;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.financial.legalentity.LegalEntity;
import com.opengamma.analytics.financial.legalentity.LegalEntityFilter;
import com.opengamma.analytics.financial.provider.calculator.generic.LastTimeCalculator;
import com.opengamma.analytics.financial.provider.calculator.issuer.ParSpreadMarketQuoteCurveSensitivityIssuerDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.issuer.ParSpreadMarketQuoteIssuerDiscountingCalculator;
import com.opengamma.analytics.financial.provider.curve.CurveBuildingBlockBundle;
import com.opengamma.analytics.financial.provider.curve.MultiCurveBundle;
import com.opengamma.analytics.financial.provider.curve.SingleCurveBundle;
import com.opengamma.analytics.financial.provider.curve.issuer.IssuerDiscountBuildingRepository;
import com.opengamma.analytics.financial.provider.description.interestrate.IssuerProviderDiscount;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.util.time.TimeCalculator;
import com.opengamma.core.link.ConventionLink;
import com.opengamma.core.link.SecurityLink;
import com.opengamma.core.marketdatasnapshot.SnapshotDataBundle;
import com.opengamma.financial.analytics.curve.AbstractCurveDefinition;
import com.opengamma.financial.analytics.curve.AbstractCurveSpecification;
import com.opengamma.financial.analytics.curve.ConverterUtils;
import com.opengamma.financial.analytics.curve.CurveConstructionConfiguration;
import com.opengamma.financial.analytics.curve.CurveGroupConfiguration;
import com.opengamma.financial.analytics.curve.CurveSpecification;
import com.opengamma.financial.analytics.curve.CurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.DiscountingCurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.FixedDateInterpolatedCurveDefinition;
import com.opengamma.financial.analytics.curve.IborCurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.InterpolatedCurveDefinition;
import com.opengamma.financial.analytics.curve.IssuerCurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.OvernightCurveTypeConfiguration;
import com.opengamma.financial.analytics.ircurve.strips.CurveNodeWithIdentifier;
import com.opengamma.financial.convention.IborIndexConvention;
import com.opengamma.financial.convention.OvernightIndexConvention;
import com.opengamma.financial.security.index.OvernightIndex;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.sesame.marketdata.IssuerMulticurveMarketDataBuilder;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.result.Result;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
/**
* Utility class for building elements of curve bundles
*
* @deprecated issuer curves are built using {@link IssuerMulticurveMarketDataBuilder}.
*/
public final class CurveBundleProviderFn {
private final CurveNodeConverterFn _curveNodeConverter;
private final CurveSpecificationFn _curveSpecificationProvider;
private final CurveSpecificationMarketDataFn _curveSpecMarketDataProvider;
private final CurveNodeInstrumentDefinitionFactory _curveNodeInstrumentDefinitionFactory;
private static final ParSpreadMarketQuoteIssuerDiscountingCalculator DISCOUNTING_CALCULATOR =
ParSpreadMarketQuoteIssuerDiscountingCalculator.getInstance();
private static final ParSpreadMarketQuoteCurveSensitivityIssuerDiscountingCalculator CURVE_SENSITIVITY_CALCULATOR =
ParSpreadMarketQuoteCurveSensitivityIssuerDiscountingCalculator.getInstance();
/**
* Creates the curve bundle provider.
*
* @param curveNodeConverter converter for curve nodes, not null.
* @param curveSpecificationProvider provides the curve spec, not null.
* @param curveSpecMarketDataProvider market data required for a curve specification, not null.
* @param curveNodeInstrumentDefinitionFactory factory to build node definitions, not null.
*
*/
public CurveBundleProviderFn(CurveNodeConverterFn curveNodeConverter,
CurveSpecificationFn curveSpecificationProvider,
CurveSpecificationMarketDataFn curveSpecMarketDataProvider,
CurveNodeInstrumentDefinitionFactory curveNodeInstrumentDefinitionFactory) {
_curveNodeConverter = ArgumentChecker.notNull(curveNodeConverter, "curveNodeConverter");
_curveSpecificationProvider = ArgumentChecker.notNull(curveSpecificationProvider, "curveSpecificationProvider");
_curveSpecMarketDataProvider = ArgumentChecker.notNull(curveSpecMarketDataProvider, "curveSpecMarketDataProvider");
_curveNodeInstrumentDefinitionFactory =
ArgumentChecker.notNull(curveNodeInstrumentDefinitionFactory, "curveNodeInstrumentDefinitionFactory");
}
private SnapshotDataBundle createSnapshotDataBundle(Map<ExternalIdBundle, Double> marketData) {
SnapshotDataBundle snapshotDataBundle = new SnapshotDataBundle();
for (Map.Entry<ExternalIdBundle, Double> entry : marketData.entrySet()) {
snapshotDataBundle.setDataPoint(entry.getKey(), entry.getValue());
}
return snapshotDataBundle;
}
private GeneratorYDCurve getGenerator(final AbstractCurveDefinition definition, LocalDate valuationDate) {
if (definition instanceof InterpolatedCurveDefinition) {
InterpolatedCurveDefinition interpolatedDefinition = (InterpolatedCurveDefinition) definition;
String interpolatorName = interpolatedDefinition.getInterpolatorName();
String leftExtrapolatorName = interpolatedDefinition.getLeftExtrapolatorName();
String rightExtrapolatorName = interpolatedDefinition.getRightExtrapolatorName();
Interpolator1D interpolator = CombinedInterpolatorExtrapolatorFactory.getInterpolator(interpolatorName,
leftExtrapolatorName,
rightExtrapolatorName);
if (definition instanceof FixedDateInterpolatedCurveDefinition) {
FixedDateInterpolatedCurveDefinition fixedDateDefinition = (FixedDateInterpolatedCurveDefinition) definition;
List<LocalDate> fixedDates = fixedDateDefinition.getFixedDates();
DoubleArrayList nodePoints = new DoubleArrayList(fixedDates.size()); //TODO what about equal node points?
for (final LocalDate fixedDate : fixedDates) {
//TODO what to do if the fixed date is before the valuation date?
nodePoints.add(TimeCalculator.getTimeBetween(valuationDate, fixedDate));
}
final double anchor = nodePoints.get(0); //TODO should the anchor go into the definition?
return new GeneratorCurveYieldInterpolatedAnchorNode(nodePoints.toDoubleArray(), anchor, interpolator);
}
return new GeneratorCurveYieldInterpolated(LastTimeCalculator.getInstance(), interpolator);
}
throw new OpenGammaRuntimeException("Cannot handle curves of type " + definition.getClass());
}
private IndexON createOvernightIndex(OvernightCurveTypeConfiguration type) {
OvernightIndex index = SecurityLink.resolvable(type.getConvention().toBundle(), OvernightIndex.class).resolve();
OvernightIndexConvention indexConvention =
ConventionLink.resolvable(index.getConventionId(), OvernightIndexConvention.class).resolve();
return ConverterUtils.indexON(index.getName(), indexConvention);
}
private IborIndex createIborIndex(IborCurveTypeConfiguration type) {
com.opengamma.financial.security.index.IborIndex indexSecurity =
SecurityLink.resolvable(type.getConvention(), com.opengamma.financial.security.index.IborIndex.class).resolve();
IborIndexConvention indexConvention =
ConventionLink.resolvable(indexSecurity.getConventionId(), IborIndexConvention.class).resolve();
return ConverterUtils.indexIbor(indexSecurity.getName(), indexConvention, indexSecurity.getTenor());
}
//TODO refactor this PLT-458
public Result<Pair<IssuerProviderDiscount, CurveBuildingBlockBundle>> getCurves(
Environment env, CurveConstructionConfiguration config, IssuerProviderDiscount exogenousBundle,
FXMatrix fxMatrix, Set<String> impliedCurveNames, IssuerDiscountBuildingRepository builder) {
final int nGroups = config.getCurveGroups().size();
@SuppressWarnings("unchecked")
MultiCurveBundle<GeneratorYDCurve>[] curveBundles = new MultiCurveBundle[nGroups];
LinkedHashMap<String, Currency> discountingMap = new LinkedHashMap<>();
LinkedHashMap<String, IborIndex[]> forwardIborMap = new LinkedHashMap<>();
LinkedHashMap<String, IndexON[]> forwardONMap = new LinkedHashMap<>();
LinkedListMultimap<String, Pair<Object, LegalEntityFilter<LegalEntity>>> issuerMap = LinkedListMultimap.create();
//TODO comparator to sort groups by order
int i = 0; // Implementation Note: loop on the groups
// Result to allow us to capture any failures in all these loops, the
// actual value if a success is of no consequence
Result<Boolean> curveBundleResult = Result.success(true);
for (final CurveGroupConfiguration group : config.getCurveGroups()) { // Group - start
final int nCurves = group.getTypesForCurves().size();
@SuppressWarnings("unchecked")
final SingleCurveBundle<GeneratorYDCurve>[] singleCurves = new SingleCurveBundle[nCurves];
int j = 0;
for (final Map.Entry<AbstractCurveDefinition, List<? extends CurveTypeConfiguration>> entry :
group.resolveTypesForCurves().entrySet()) {
AbstractCurveDefinition curve = entry.getKey();
String curveName = curve.getName();
//TODO PLAT-6800 check implied curves first
Result<AbstractCurveSpecification> curveSpecResult =
_curveSpecificationProvider.getCurveSpecification(env, curve);
if (curveSpecResult.isSuccess()) {
final CurveSpecification specification = (CurveSpecification) curveSpecResult.getValue();
Result<Map<ExternalIdBundle, Double>> marketDataResult =
_curveSpecMarketDataProvider.requestData(env, specification);
// Only proceed if we have all market data values available to us
if (marketDataResult.isSuccess()) {
// todo this is temporary to allow us to get up and running fast
SnapshotDataBundle snapshot = createSnapshotDataBundle(marketDataResult.getValue());
int nNodes = specification.getNodes().size();
double[] parameterGuessForCurves = new double[nNodes];
// For FX forward, the FX rate is not a good initial guess. // TODO: change this // marketData
Arrays.fill(parameterGuessForCurves, 0.02);
Result<InstrumentDerivative[]> derivativesForCurve =
extractInstrumentDerivatives(env, specification, snapshot, fxMatrix, env.getValuationTime());
List<IborIndex> iborIndex = new ArrayList<>();
List<IndexON> overnightIndex = new ArrayList<>();
for (final CurveTypeConfiguration type : entry.getValue()) {
if (type instanceof DiscountingCurveTypeConfiguration) {
final String reference = ((DiscountingCurveTypeConfiguration) type).getReference();
try {
Currency currency = Currency.of(reference);
//should this map check that the curve name has not already been entered?
discountingMap.put(curveName, currency);
} catch (final IllegalArgumentException e) {
throw new OpenGammaRuntimeException("Cannot handle reference type " + reference
+ " for discounting curves");
}
} else if (type instanceof IborCurveTypeConfiguration) {
iborIndex.add(createIborIndex((IborCurveTypeConfiguration) type));
} else if (type instanceof OvernightCurveTypeConfiguration) {
overnightIndex.add(createOvernightIndex((OvernightCurveTypeConfiguration) type));
} else if (type instanceof IssuerCurveTypeConfiguration) {
final IssuerCurveTypeConfiguration issuer = (IssuerCurveTypeConfiguration) type;
issuerMap.put(curveName, Pairs.<Object, LegalEntityFilter<LegalEntity>>of(issuer.getKeys(), issuer.getFilters()));
} else {
Result<?> typeFailure = Result.failure(ERROR, "Cannot handle curveTypeConfiguration with type {} " +
"whilst building curve: {}", type.getClass(), curveName);
curveBundleResult = Result.failure(curveBundleResult, typeFailure);
}
}
if (!iborIndex.isEmpty()) {
forwardIborMap.put(curveName, iborIndex.toArray(new IborIndex[iborIndex.size()]));
}
if (!overnightIndex.isEmpty()) {
forwardONMap.put(curveName, overnightIndex.toArray(new IndexON[overnightIndex.size()]));
}
if (derivativesForCurve.isSuccess()) {
GeneratorYDCurve generator = getGenerator(curve, env.getValuationDate());
singleCurves[j] = new SingleCurveBundle<>(curveName,
derivativesForCurve.getValue(),
generator.initialGuess(parameterGuessForCurves),
generator);
} else {
curveBundleResult = Result.failure(curveBundleResult, derivativesForCurve);
}
} else {
curveBundleResult = Result.failure(curveBundleResult, marketDataResult);
}
} else {
curveBundleResult = Result.failure(curveBundleResult, curveSpecResult);
}
j++;
}
if (curveBundleResult.isSuccess()) {
curveBundles[i++] = new MultiCurveBundle<>(singleCurves);
}
} // Group - end
if (curveBundleResult.isSuccess()) {
//TODO PLAT-6800 remove implied curves
Pair<IssuerProviderDiscount, CurveBuildingBlockBundle> calibratedCurves =
builder.makeCurvesFromDerivatives(curveBundles,
exogenousBundle.getIssuerProvider(),
discountingMap,
forwardIborMap,
forwardONMap,
issuerMap,
DISCOUNTING_CALCULATOR,
CURVE_SENSITIVITY_CALCULATOR);
return Result.success(calibratedCurves);
} else {
return Result.failure(curveBundleResult);
}
}
private Result<InstrumentDerivative[]> extractInstrumentDerivatives(Environment env,
CurveSpecification specification,
SnapshotDataBundle snapshot,
FXMatrix fxMatrix,
ZonedDateTime valuationTime) {
Set<CurveNodeWithIdentifier> nodes = specification.getNodes();
List<InstrumentDerivative> derivativesForCurve = new ArrayList<>(nodes.size());
List<Result<?>> failures = new ArrayList<>();
for (CurveNodeWithIdentifier node : nodes) {
InstrumentDefinition<?> definitionForNode =
_curveNodeInstrumentDefinitionFactory.createInstrumentDefinition(node, snapshot, valuationTime, fxMatrix);
Result<InstrumentDerivative> derivativeResult =
_curveNodeConverter.getDerivative(env, node, definitionForNode, valuationTime);
if (derivativeResult.isSuccess()) {
derivativesForCurve.add(derivativeResult.getValue());
} else {
failures.add(derivativeResult);
}
}
if (failures.isEmpty()) {
return Result.success(derivativesForCurve.toArray(new InstrumentDerivative[derivativesForCurve.size()]));
} else {
return Result.failure(failures);
}
}
}