/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.curve;
import static com.opengamma.engine.value.ValuePropertyNames.CURVE;
import static com.opengamma.engine.value.ValuePropertyNames.CURVE_SENSITIVITY_CURRENCY;
import static com.opengamma.engine.value.ValueRequirementNames.CURVE_INSTRUMENT_CONVERSION_HISTORICAL_TIME_SERIES;
import static com.opengamma.engine.value.ValueRequirementNames.FX_MATRIX;
import static com.opengamma.engine.value.ValueRequirementNames.YIELD_CURVE;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.OpenGammaRuntimeException;
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.interestrate.InstrumentDerivativeVisitor;
import com.opengamma.analytics.financial.model.interestrate.curve.DiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldPeriodicCurve;
import com.opengamma.analytics.financial.provider.curve.CurveBuildingBlock;
import com.opengamma.analytics.financial.provider.curve.CurveBuildingBlockBundle;
import com.opengamma.analytics.financial.provider.curve.multicurve.MulticurveDiscountBuildingRepository;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MulticurveSensitivity;
import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.analytics.util.time.TimeCalculator;
import com.opengamma.core.convention.ConventionSource;
import com.opengamma.core.marketdatasnapshot.SnapshotDataBundle;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueRequirementNames;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.OpenGammaExecutionContext;
import com.opengamma.financial.analytics.curve.CurveConstructionConfiguration;
import com.opengamma.financial.analytics.curve.CurveDefinition;
import com.opengamma.financial.analytics.curve.CurveGroupConfiguration;
import com.opengamma.financial.analytics.curve.CurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.DiscountingCurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.IborCurveTypeConfiguration;
import com.opengamma.financial.analytics.curve.InterpolatedCurveSpecification;
import com.opengamma.financial.analytics.ircurve.strips.ContinuouslyCompoundedRateNode;
import com.opengamma.financial.analytics.ircurve.strips.CurveNode;
import com.opengamma.financial.analytics.ircurve.strips.CurveNodeVisitor;
import com.opengamma.financial.analytics.ircurve.strips.CurveNodeWithIdentifier;
import com.opengamma.financial.analytics.ircurve.strips.DiscountFactorNode;
import com.opengamma.financial.analytics.ircurve.strips.PeriodicallyCompoundedRateNode;
import com.opengamma.financial.analytics.model.InterpolatedDataProperties;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesBundle;
import com.opengamma.financial.convention.IborIndexConvention;
import com.opengamma.id.ExternalId;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.time.Tenor;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Produces yield curves using the {@link InterpolatedDataProperties#CALCULATION_METHOD_NAME} method.
*/
public class MultiCurveInterpolatedFunction extends
MultiCurveFunction<MulticurveProviderInterface, MulticurveDiscountBuildingRepository, GeneratorYDCurve, MulticurveSensitivity> {
/** The logger */
private static final Logger s_logger = LoggerFactory.getLogger(MultiCurveInterpolatedFunction.class);
/**
* @param curveConfigurationName The curve configuration name, not null
*/
public MultiCurveInterpolatedFunction(final String curveConfigurationName) {
super(curveConfigurationName);
}
@Override
protected String getCurveTypeProperty() {
return InterpolatedDataProperties.CALCULATION_METHOD_NAME;
}
@Override
protected InstrumentDerivativeVisitor<MulticurveProviderInterface, Double> getCalculator() {
throw new UnsupportedOperationException("Curves created with the Interpolated method do not use a calculator");
}
@Override
protected InstrumentDerivativeVisitor<MulticurveProviderInterface, MulticurveSensitivity> getSensitivityCalculator() {
throw new UnsupportedOperationException("Curves created with the Interpolated method do not use a sensitivity calculator");
}
@Override
public CompiledFunctionDefinition getCompiledFunction(final ZonedDateTime earliestInvocation, final ZonedDateTime latestInvocation, final String[] curveNames,
final Set<ValueRequirement> exogenousRequirements, final CurveConstructionConfiguration curveConstructionConfiguration) {
return new MultiCurveInterpolatedCompiledFunctionDefinition(earliestInvocation, latestInvocation, curveNames, exogenousRequirements, curveConstructionConfiguration);
}
@Override
public CompiledFunctionDefinition getCompiledFunction(ZonedDateTime earliestInvocation, ZonedDateTime latestInvocation, String[] curveNames,
Set<ValueRequirement> exogenousRequirements, CurveConstructionConfiguration curveConstructionConfiguration,
String[] currencies) {
return new MultiCurveInterpolatedCompiledFunctionDefinition(earliestInvocation, latestInvocation, curveNames, exogenousRequirements, curveConstructionConfiguration, currencies);
}
/**
* Compiled function implementation.
*/
protected class MultiCurveInterpolatedCompiledFunctionDefinition extends CurveCompiledFunctionDefinition {
/** The curve construction configuration */
private final CurveConstructionConfiguration _curveConstructionConfiguration;
/**
* @param earliestInvocation The earliest time for which this function is valid, null if there is no bound
* @param latestInvocation The latest time for which this function is valid, null if there is no bound
* @param curveNames The names of the curves produced by this function, not null
* @param exogenousRequirements The exogenous requirements, not null
* @param curveConstructionConfiguration The curve construction configuration, not null
* @param currencies The set of currencies to which the curves produce sensitivities
*/
protected MultiCurveInterpolatedCompiledFunctionDefinition(ZonedDateTime earliestInvocation, ZonedDateTime latestInvocation,
String[] curveNames, Set<ValueRequirement> exogenousRequirements,
CurveConstructionConfiguration curveConstructionConfiguration,
String[] currencies) {
super(earliestInvocation, latestInvocation, curveNames, ValueRequirementNames.YIELD_CURVE, exogenousRequirements, currencies);
ArgumentChecker.notNull(curveConstructionConfiguration, "curve construction configuration");
_curveConstructionConfiguration = curveConstructionConfiguration;
}
@Override
public boolean canHandleMissingRequirements() {
return true;
}
@Override
public boolean canHandleMissingInputs() {
return true;
}
/**
* @param earliestInvocation The earliest time for which this function is valid, null if there is no bound
* @param latestInvocation The latest time for which this function is valid, null if there is no bound
* @param curveNames The names of the curves produced by this function, not null
* @param exogenousRequirements The exogenous requirements, not null
* @param curveConstructionConfiguration The curve construction configuration, not null
*/
protected MultiCurveInterpolatedCompiledFunctionDefinition(
final ZonedDateTime earliestInvocation,
final ZonedDateTime latestInvocation,
final String[] curveNames,
final Set<ValueRequirement> exogenousRequirements,
final CurveConstructionConfiguration curveConstructionConfiguration) {
super(earliestInvocation, latestInvocation, curveNames, ValueRequirementNames.YIELD_CURVE, exogenousRequirements);
ArgumentChecker.notNull(curveConstructionConfiguration, "curve construction configuration");
_curveConstructionConfiguration = curveConstructionConfiguration;
}
@Override
protected Pair<MulticurveProviderInterface, CurveBuildingBlockBundle> getCurves(final FunctionInputs inputs, final ZonedDateTime now, final MulticurveDiscountBuildingRepository builder,
final MulticurveProviderInterface knownData, final FunctionExecutionContext context, final FXMatrix fx) {
final ConventionSource conventionSource = OpenGammaExecutionContext.getConventionSource(context);
int n = 0;
// These loops are here because the market data snapshot might not contain all of the required information
for (final CurveGroupConfiguration group: _curveConstructionConfiguration.getCurveGroups()) {
for (final Map.Entry<String, List<? extends CurveTypeConfiguration>> entry: group.getTypesForCurves().entrySet()) {
final String curveName = entry.getKey();
final ValueProperties curveProperties = ValueProperties.builder().with(CURVE, curveName).get();
final InterpolatedCurveSpecification specification =
(InterpolatedCurveSpecification) inputs.getValue(new ValueRequirement(ValueRequirementNames.CURVE_SPECIFICATION, ComputationTargetSpecification.NULL, curveProperties));
n += specification.getNodes().size();
}
}
final MulticurveProviderDiscount curveBundle = (MulticurveProviderDiscount) getKnownData(inputs);
final LinkedHashMap<String, Pair<CurveBuildingBlock, DoubleMatrix2D>> unitBundles = new LinkedHashMap<>();
for (final CurveGroupConfiguration group: _curveConstructionConfiguration.getCurveGroups()) {
for (final Map.Entry<String, List<? extends CurveTypeConfiguration>> entry: group.getTypesForCurves().entrySet()) {
final LinkedHashMap<String, Pair<Integer, Integer>> unitMap = new LinkedHashMap<>();
int totalNodes = 0;
final String curveName = entry.getKey();
final List<? extends CurveTypeConfiguration> types = entry.getValue();
final ValueProperties curveProperties = ValueProperties.builder().with(CURVE, curveName).get();
final Object dataObject = inputs.getValue(new ValueRequirement(ValueRequirementNames.CURVE_MARKET_DATA, ComputationTargetSpecification.NULL, curveProperties));
if (dataObject == null) {
throw new OpenGammaRuntimeException("Could not get yield curve data");
}
final SnapshotDataBundle marketData = (SnapshotDataBundle) dataObject;
final InterpolatedCurveSpecification specification =
(InterpolatedCurveSpecification) inputs.getValue(new ValueRequirement(ValueRequirementNames.CURVE_SPECIFICATION, ComputationTargetSpecification.NULL, curveProperties));
n = specification.getNodes().size();
final double[] times = new double[n];
final double[] yields = new double[n];
final double[][] jacobian = new double[n][n];
boolean isYield = false;
int i = 0;
int compoundPeriodsPerYear = 0;
final int nNodesForCurve = specification.getNodes().size();
for (final CurveNodeWithIdentifier node: specification.getNodes()) {
final CurveNode curveNode = node.getCurveNode();
if (curveNode instanceof ContinuouslyCompoundedRateNode) {
if (i == 0) {
isYield = true;
} else {
if (!isYield) {
throw new OpenGammaRuntimeException("Was expecting only continuously-compounded rate nodes; have " + curveNode);
}
}
} else if (curveNode instanceof DiscountFactorNode) {
if (i == 0) {
isYield = false;
} else {
if (isYield) {
throw new OpenGammaRuntimeException("Was expecting only discount factor nodes; have " + curveNode);
}
}
} else if (curveNode instanceof PeriodicallyCompoundedRateNode) {
if (i == 0) {
compoundPeriodsPerYear = ((PeriodicallyCompoundedRateNode) curveNode).getCompoundingPeriodsPerYear();
isYield = true;
} else {
if (!isYield) {
throw new OpenGammaRuntimeException("Was expecting only periodically compounded nodes; have " + curveNode);
}
}
} else {
throw new OpenGammaRuntimeException("Can only handle discount factor or continuously-compounded rate nodes; have " + curveNode);
}
final Double marketValue = marketData.getDataPoint(node.getIdentifier());
if (marketValue == null) {
throw new OpenGammaRuntimeException("Could not get market value for " + node);
}
final Tenor maturity = curveNode.getResolvedMaturity();
times[i] = TimeCalculator.getTimeBetween(now, now.plus(maturity.getPeriod()));
yields[i] = marketValue;
jacobian[i][i] = 1;
i++;
}
final String interpolatorName = specification.getInterpolatorName();
final String rightExtrapolatorName = specification.getRightExtrapolatorName();
final String leftExtrapolatorName = specification.getLeftExtrapolatorName();
final Interpolator1D interpolator = CombinedInterpolatorExtrapolatorFactory.getInterpolator(interpolatorName, leftExtrapolatorName, rightExtrapolatorName);
final InterpolatedDoublesCurve rawCurve = InterpolatedDoublesCurve.from(times, yields, interpolator, curveName);
final YieldAndDiscountCurve discountCurve;
if (compoundPeriodsPerYear != 0 && isYield) {
discountCurve = YieldPeriodicCurve.from(compoundPeriodsPerYear, rawCurve);
} else if (isYield) {
discountCurve = new YieldCurve(curveName, rawCurve);
} else {
discountCurve = new DiscountCurve(curveName, rawCurve);
}
for (final CurveTypeConfiguration type: types) {
if (type instanceof DiscountingCurveTypeConfiguration) {
final Currency currency = Currency.parse(((DiscountingCurveTypeConfiguration) type).getReference());
curveBundle.setCurve(currency, discountCurve);
} else if (type instanceof IborCurveTypeConfiguration) {
final IborIndexConvention iborIndexConvention = conventionSource.getSingle(((IborCurveTypeConfiguration) type).getConvention(), IborIndexConvention.class);
final Tenor iborIndexTenor = ((IborCurveTypeConfiguration) type).getTenor();
final int spotLag = iborIndexConvention.getSettlementDays();
final IborIndex index = new IborIndex(iborIndexConvention.getCurrency(), iborIndexTenor.getPeriod(), spotLag, iborIndexConvention.getDayCount(),
iborIndexConvention.getBusinessDayConvention(), iborIndexConvention.isIsEOM(), iborIndexConvention.getName());
curveBundle.setCurve(index, discountCurve);
}
}
unitMap.put(curveName, Pairs.of(totalNodes, nNodesForCurve));
unitBundles.put(curveName, Pairs.of(new CurveBuildingBlock(unitMap), new DoubleMatrix2D(jacobian)));
totalNodes += nNodesForCurve;
}
}
return Pairs.of((MulticurveProviderInterface) curveBundle, new CurveBuildingBlockBundle(unitBundles));
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext compilationContext, final ComputationTarget target, final ValueRequirement desiredValue) {
final Set<ValueRequirement> requirements = super.getRequirements(compilationContext, target, desiredValue);
if (requirements == null) {
return null;
}
final Set<ValueRequirement> trimmed = new HashSet<>();
for (final ValueRequirement requirement : requirements) {
final String requirementName = requirement.getValueName();
if (!(requirementName.equals(CURVE_INSTRUMENT_CONVERSION_HISTORICAL_TIME_SERIES) || requirementName.equals(FX_MATRIX))) {
trimmed.add(requirement);
}
}
return requirements;
}
@Override
protected MulticurveProviderInterface getKnownData(final FunctionInputs inputs) {
final FXMatrix fxMatrix = new FXMatrix();
MulticurveProviderDiscount knownData;
if (getExogenousRequirements().isEmpty()) {
knownData = new MulticurveProviderDiscount(fxMatrix);
} else {
knownData = (MulticurveProviderDiscount) inputs.getValue(ValueRequirementNames.CURVE_BUNDLE);
knownData.setForexMatrix(fxMatrix);
}
return knownData;
}
@Override
protected MulticurveDiscountBuildingRepository getBuilder(final double absoluteTolerance, final double relativeTolerance, final int maxIterations) {
// Returns null because builder is not used
return null;
}
@Override
protected GeneratorYDCurve getGenerator(final CurveDefinition definition, final LocalDate valuationDate) {
// Returns null because generator is not used
return null;
}
@Override
protected CurveNodeVisitor<InstrumentDefinition<?>> getCurveNodeConverter(final FunctionExecutionContext context, final SnapshotDataBundle marketData,
final ExternalId dataId, final HistoricalTimeSeriesBundle historicalData, final ZonedDateTime valuationTime, final FXMatrix fxMatrix) {
// No need to convert to InstrumentDefinition if we are not fitting the curve.
return null;
}
@Override
protected Set<ComputedValue> getResults(final ValueSpecification bundleSpec, final ValueSpecification jacobianSpec, final ValueProperties bundleProperties,
final Pair<MulticurveProviderInterface, CurveBuildingBlockBundle> pair) {
final Set<ComputedValue> result = new HashSet<>();
final MulticurveProviderDiscount provider = (MulticurveProviderDiscount) pair.getFirst();
result.add(new ComputedValue(bundleSpec, provider));
result.add(new ComputedValue(jacobianSpec, pair.getSecond()));
for (final String curveName : getCurveNames()) {
final ValueProperties curveProperties = bundleProperties.copy()
.with(CurveCalculationPropertyNamesAndValues.PROPERTY_CURVE_TYPE, getCurveTypeProperty())
.withoutAny(CURVE)
.withoutAny(CURVE_SENSITIVITY_CURRENCY)
.with(CURVE, curveName)
.get();
final YieldAndDiscountCurve curve = provider.getCurve(curveName);
if (curve == null) {
s_logger.error("Could not get curve called {} from configuration {}", curveName, getCurveConstructionConfigurationName());
} else {
final ValueSpecification curveSpec = new ValueSpecification(YIELD_CURVE, ComputationTargetSpecification.NULL, curveProperties);
result.add(new ComputedValue(curveSpec, curve));
}
}
return result;
}
}
}