/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.measure.curve;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.collect.Guavate.toImmutableMap;
import static com.opengamma.strata.collect.Guavate.toImmutableSet;
import static com.opengamma.strata.collect.Guavate.zip;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.calc.marketdata.MarketDataConfig;
import com.opengamma.strata.calc.marketdata.MarketDataFunction;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.collect.MapStream;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.data.MarketDataId;
import com.opengamma.strata.data.ObservableSource;
import com.opengamma.strata.data.scenario.MarketDataBox;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
import com.opengamma.strata.market.curve.CurveGroupDefinition;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveInputs;
import com.opengamma.strata.market.curve.CurveInputsId;
import com.opengamma.strata.market.curve.CurveMetadata;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.NodalCurveDefinition;
/**
* Market data function that builds the input data used when calibrating a curve.
*/
public final class CurveInputsMarketDataFunction implements MarketDataFunction<CurveInputs, CurveInputsId> {
@Override
public MarketDataRequirements requirements(CurveInputsId id, MarketDataConfig marketDataConfig) {
CurveGroupDefinition groupConfig = marketDataConfig.get(CurveGroupDefinition.class, id.getCurveGroupName());
Optional<NodalCurveDefinition> optionalDefinition = groupConfig.findCurveDefinition(id.getCurveName());
if (!optionalDefinition.isPresent()) {
return MarketDataRequirements.empty();
}
NodalCurveDefinition definition = optionalDefinition.get();
return MarketDataRequirements.builder().addValues(nodeRequirements(ImmutableList.of(definition))).build();
}
@Override
public MarketDataBox<CurveInputs> build(
CurveInputsId id,
MarketDataConfig marketDataConfig,
ScenarioMarketData marketData,
ReferenceData refData) {
CurveGroupName groupName = id.getCurveGroupName();
CurveName curveName = id.getCurveName();
CurveGroupDefinition groupDefn = marketDataConfig.get(CurveGroupDefinition.class, groupName);
Optional<NodalCurveDefinition> optionalDefinition = groupDefn.findCurveDefinition(id.getCurveName());
if (!optionalDefinition.isPresent()) {
throw new IllegalArgumentException(Messages.format("No curve named '{}' found in group '{}'", curveName, groupName));
}
NodalCurveDefinition configuredDefn = optionalDefinition.get();
// determine market data needs
MarketDataBox<LocalDate> valuationDates = marketData.getValuationDate();
boolean multipleValuationDates = valuationDates.isScenarioValue();
// curve definition can vary for each valuation date
if (multipleValuationDates) {
List<NodalCurveDefinition> curveDefns = IntStream.range(0, valuationDates.getScenarioCount())
.mapToObj(valuationDates::getValue)
.map((LocalDate valDate) -> configuredDefn.filtered(valDate, refData))
.collect(toImmutableList());
Set<? extends MarketDataId<?>> requirements = nodeRequirements(curveDefns);
ObservableSource obsSource = id.getObservableSource();
Map<? extends MarketDataId<?>, MarketDataBox<?>> marketDataValues =
getMarketDataValues(marketData, requirements, obsSource);
return buildMultipleCurveInputs(MarketDataBox.ofScenarioValues(curveDefns), marketDataValues, valuationDates, refData);
}
// only one valuation date
LocalDate valuationDate = valuationDates.getValue(0);
NodalCurveDefinition filteredDefn = configuredDefn.filtered(valuationDate, refData);
Set<? extends MarketDataId<?>> requirements = nodeRequirements(ImmutableList.of(filteredDefn));
ObservableSource obsSource = id.getObservableSource();
Map<? extends MarketDataId<?>, MarketDataBox<?>> marketDataValues =
getMarketDataValues(marketData, requirements, obsSource);
// Do any of the inputs contain values for multiple scenarios, or do they contain 1 value each?
boolean multipleInputValues = marketDataValues.values().stream().anyMatch(MarketDataBox::isScenarioValue);
return multipleInputValues || multipleValuationDates ?
buildMultipleCurveInputs(MarketDataBox.ofSingleValue(filteredDefn), marketDataValues, valuationDates, refData) :
buildSingleCurveInputs(filteredDefn, marketDataValues, valuationDate, refData);
}
// one valuation date, one set of market data
private MarketDataBox<CurveInputs> buildSingleCurveInputs(
NodalCurveDefinition filteredDefn,
Map<? extends MarketDataId<?>, MarketDataBox<?>> marketData,
LocalDate valuationDate,
ReferenceData refData) {
// There is only a single map of values and single valuation date - create a single CurveInputs instance
CurveMetadata curveMetadata = filteredDefn.metadata(valuationDate, refData);
Map<? extends MarketDataId<?>, ?> singleMarketDataValues = MapStream.of(marketData)
.mapValues(box -> box.getSingleValue())
.toMap();
CurveInputs curveInputs = CurveInputs.of(singleMarketDataValues, curveMetadata);
return MarketDataBox.ofSingleValue(curveInputs);
}
// one valuation date, scenario market data
private MarketDataBox<CurveInputs> buildMultipleCurveInputs(
MarketDataBox<NodalCurveDefinition> filteredDefns,
Map<? extends MarketDataId<?>, MarketDataBox<?>> marketData,
MarketDataBox<LocalDate> valuationDates,
ReferenceData refData) {
// If there are multiple values for any of the input data values or for the valuation
// dates then we need to create multiple sets of inputs
int scenarioCount = scenarioCount(valuationDates, marketData);
ImmutableList.Builder<CurveMetadata> curveMetadataBuilder = ImmutableList.builder();
for (int i = 0; i < scenarioCount; i++) {
LocalDate valDate = valuationDates.getValue(i);
NodalCurveDefinition defn = filteredDefns.getValue(i);
curveMetadataBuilder.add(defn.metadata(valDate, refData));
}
List<CurveMetadata> curveMetadata = curveMetadataBuilder.build();
List<Map<? extends MarketDataId<?>, ?>> scenarioValues = IntStream.range(0, scenarioCount)
.mapToObj(i -> buildScenarioValues(marketData, i))
.collect(toImmutableList());
List<CurveInputs> curveInputs = zip(scenarioValues.stream(), curveMetadata.stream())
.map(pair -> CurveInputs.of(pair.getFirst(), pair.getSecond()))
.collect(toImmutableList());
return MarketDataBox.ofScenarioValues(curveInputs);
}
/**
* Builds a map of market data identifier to market data value for a single scenario.
*
* @param values the market data values for all scenarios
* @param scenarioIndex the index of the scenario
* @return map of market data values for one scenario
*/
private static Map<? extends MarketDataId<?>, ?> buildScenarioValues(
Map<? extends MarketDataId<?>, MarketDataBox<?>> values,
int scenarioIndex) {
return MapStream.of(values).mapValues(box -> box.getValue(scenarioIndex)).toMap();
}
private static int scenarioCount(
MarketDataBox<LocalDate> valuationDate,
Map<? extends MarketDataId<?>, MarketDataBox<?>> marketData) {
int scenarioCount = 0;
if (valuationDate.isScenarioValue()) {
scenarioCount = valuationDate.getScenarioCount();
}
for (Map.Entry<? extends MarketDataId<?>, MarketDataBox<?>> entry : marketData.entrySet()) {
MarketDataBox<?> box = entry.getValue();
MarketDataId<?> id = entry.getKey();
if (box.isScenarioValue()) {
int boxScenarioCount = box.getScenarioCount();
if (scenarioCount == 0) {
scenarioCount = boxScenarioCount;
} else {
if (scenarioCount != boxScenarioCount) {
throw new IllegalArgumentException(
Messages.format(
"There are {} scenarios for ID {} which does not match the previous scenario count {}",
boxScenarioCount,
id,
scenarioCount));
}
}
}
}
if (scenarioCount != 0) {
return scenarioCount;
}
// This shouldn't happen, this method is only called after checking at least one of the values contains data
// for multiple scenarios.
throw new IllegalArgumentException("Cannot count the scenarios, all data contained single values");
}
@Override
public Class<CurveInputsId> getMarketDataIdType() {
return CurveInputsId.class;
}
/**
* Returns requirements for the market data needed by the curve nodes to build trades.
*
* @param curveDefn the curve definition containing the nodes
* @return requirements for the market data needed by the nodes to build trades
*/
private static Set<? extends MarketDataId<?>> nodeRequirements(List<NodalCurveDefinition> curveDefns) {
return curveDefns.stream()
.flatMap(defn -> defn.getNodes().stream())
.flatMap(node -> node.requirements().stream())
.collect(toImmutableSet());
}
private static Map<? extends MarketDataId<?>, MarketDataBox<?>> getMarketDataValues(
ScenarioMarketData marketData,
Set<? extends MarketDataId<?>> ids,
ObservableSource observableSource) {
return ids.stream().collect(toImmutableMap(k -> k, k -> marketData.getValue(k)));
}
}