com.opengamma.strata.calc.runner.CalculationTask Maven / Gradle / Ivy
Show all versions of strata-calc Show documentation
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.calc.runner;
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 java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.TypedMetaBean;
import org.joda.beans.gen.BeanDefinition;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.light.LightMetaBean;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.opengamma.strata.basics.CalculationTarget;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.ReferenceDataNotFoundException;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyPair;
import com.opengamma.strata.basics.currency.FxRate;
import com.opengamma.strata.calc.Measure;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.calc.marketdata.MarketDataRequirementsBuilder;
import com.opengamma.strata.collect.result.FailureReason;
import com.opengamma.strata.collect.result.Result;
import com.opengamma.strata.data.FxRateId;
import com.opengamma.strata.data.MarketDataId;
import com.opengamma.strata.data.MarketDataNotFoundException;
import com.opengamma.strata.data.ObservableId;
import com.opengamma.strata.data.ObservableSource;
import com.opengamma.strata.data.scenario.ScenarioFxRateProvider;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
/**
* A single task that will be used to perform a calculation.
*
* This is a single unit of execution in the calculation runner.
* It consists of a {@link CalculationFunction} and the appropriate inputs,
* including a single {@link CalculationTarget}. When invoked, it will
* calculate a result for one or more columns in the grid of results.
*/
@BeanDefinition(style = "light")
public final class CalculationTask implements ImmutableBean {
/**
* The target for which the value will be calculated.
* This is typically a trade.
*/
@PropertyDefinition(validate = "notNull")
private final CalculationTarget target;
/**
* The function that will calculate the value.
*/
@PropertyDefinition(validate = "notNull")
private final CalculationFunction function;
/**
* The additional parameters.
*/
@PropertyDefinition(validate = "notNull")
private final CalculationParameters parameters;
/**
* The cells to be calculated.
*/
@PropertyDefinition(validate = "notEmpty")
private final List cells;
//-------------------------------------------------------------------------
/**
* Obtains an instance that will calculate the specified cells.
*
* The cells must all be for the same row index and none of the column indices must overlap.
* The result will contain no calculation parameters.
*
* @param target the target for which the value will be calculated
* @param function the function that performs the calculation
* @param cells the cells to be calculated by this task
* @return the task
*/
public static CalculationTask of(
CalculationTarget target,
CalculationFunction extends CalculationTarget> function,
CalculationTaskCell... cells) {
return of(target, function, CalculationParameters.empty(), ImmutableList.copyOf(cells));
}
/**
* Obtains an instance that will calculate the specified cells.
*
* The cells must all be for the same row index and none of the column indices must overlap.
*
* @param target the target for which the value will be calculated
* @param function the function that performs the calculation
* @param parameters the additional parameters
* @param cells the cells to be calculated by this task
* @return the task
*/
public static CalculationTask of(
CalculationTarget target,
CalculationFunction extends CalculationTarget> function,
CalculationParameters parameters,
List cells) {
@SuppressWarnings("unchecked")
CalculationFunction functionCast = (CalculationFunction) function;
return new CalculationTask(target, functionCast, parameters, cells);
}
//-------------------------------------------------------------------------
/**
* Gets the index of the row in the grid of results.
*
* @return the row index
*/
public int getRowIndex() {
return cells.get(0).getRowIndex();
}
/**
* Gets the set of measures that will be calculated by this task.
*
* @return the measures
*/
public Set getMeasures() {
return cells.stream().map(c -> c.getMeasure()).collect(toImmutableSet());
}
//-------------------------------------------------------------------------
/**
* Returns requirements specifying the market data the function needs to perform its calculations.
*
* @param refData the reference data
* @return requirements specifying the market data the function needs to perform its calculations
*/
@SuppressWarnings("unchecked")
public MarketDataRequirements requirements(ReferenceData refData) {
// determine market data requirements of the function
FunctionRequirements functionRequirements = function.requirements(target, getMeasures(), parameters, refData);
ObservableSource obsSource = functionRequirements.getObservableSource();
// convert function requirements to market data requirements
MarketDataRequirementsBuilder requirementsBuilder = MarketDataRequirements.builder();
for (ObservableId id : functionRequirements.getTimeSeriesRequirements()) {
requirementsBuilder.addTimeSeries(id.withObservableSource(obsSource));
}
for (MarketDataId> id : functionRequirements.getValueRequirements()) {
if (id instanceof ObservableId) {
requirementsBuilder.addValues(((ObservableId) id).withObservableSource(obsSource));
} else {
requirementsBuilder.addValues(id);
}
}
// add requirements for the FX rates needed to convert the output values into the reporting currency
for (CalculationTaskCell cell : cells) {
if (cell.getMeasure().isCurrencyConvertible() && !cell.getReportingCurrency().isNone()) {
Currency reportingCurrency = cell.reportingCurrency(this, refData);
List> fxRateIds = functionRequirements.getOutputCurrencies().stream()
.filter(outputCurrency -> !outputCurrency.equals(reportingCurrency))
.map(outputCurrency -> CurrencyPair.of(outputCurrency, reportingCurrency))
.map(pair -> FxRateId.of(pair, obsSource))
.collect(toImmutableList());
requirementsBuilder.addValues(fxRateIds);
}
}
return requirementsBuilder.build();
}
/**
* Determines the natural currency of the target.
*
* This is only called for measures that are currency convertible.
*
* @param refData the reference data
* @return the natural currency
*/
public Currency naturalCurrency(ReferenceData refData) {
return function.naturalCurrency(target, refData);
}
//-------------------------------------------------------------------------
/**
* Executes the task, performing calculations for the target using multiple sets of market data.
*
* This invokes the function with the correct set of market data.
*
* @param marketData the market data used in the calculation
* @param refData the reference data
* @return results of the calculation, one for every scenario in the market data
*/
@SuppressWarnings("unchecked")
public CalculationResults execute(ScenarioMarketData marketData, ReferenceData refData) {
// calculate the results
Map> results = calculate(marketData, refData);
// get a suitable FX provider
ScenarioFxRateProvider fxProvider = parameters.findParameter(FxRateLookup.class)
.map(lookup -> LookupScenarioFxRateProvider.of(marketData, lookup))
.orElse(ScenarioFxRateProvider.of(marketData));
// convert the results, using a normal loop for better stack traces
ImmutableList.Builder resultBuilder = ImmutableList.builder();
for (CalculationTaskCell cell : cells) {
resultBuilder.add(cell.createResult(this, target, results, fxProvider, refData));
}
// return the result
return CalculationResults.of(target, resultBuilder.build());
}
// calculates the result
private Map> calculate(ScenarioMarketData marketData, ReferenceData refData) {
try {
Set requestedMeasures = getMeasures();
Set supportedMeasures = function.supportedMeasures();
Set measures = Sets.intersection(requestedMeasures, supportedMeasures);
Map> map = ImmutableMap.of();
if (!measures.isEmpty()) {
map = function.calculate(target, measures, parameters, marketData, refData);
}
// check if result does not contain all requested measures
if (!map.keySet().containsAll(requestedMeasures)) {
return handleMissing(requestedMeasures, supportedMeasures, map);
}
return map;
} catch (RuntimeException ex) {
return handleFailure(ex);
}
}
// populate the result with failures
private Map> handleMissing(
Set requestedMeasures,
Set supportedMeasures,
Map> calculatedResults) {
// need to add missing measures
Map> updated = new HashMap<>(calculatedResults);
String fnName = function.getClass().getSimpleName();
for (Measure requestedMeasure : requestedMeasures) {
if (!calculatedResults.containsKey(requestedMeasure)) {
if (supportedMeasures.contains(requestedMeasure)) {
String msg = function.identifier(target)
.map(v -> "for ID '" + v + "'")
.orElse("for target '" + target.toString() + "'");
updated.put(requestedMeasure, Result.failure(
FailureReason.CALCULATION_FAILED,
"Function '{}' did not return requested measure '{}' {}",
fnName,
requestedMeasure,
msg));
} else {
updated.put(requestedMeasure, Result.failure(
FailureReason.UNSUPPORTED,
"Measure '{}' is not supported by function '{}'",
requestedMeasure,
fnName));
}
}
}
return updated;
}
// handle the failure, extracted to aid inlining
private Map> handleFailure(RuntimeException ex) {
Result> failure;
String fnName = function.getClass().getSimpleName();
String exMsg = ex.getMessage();
Optional id = function.identifier(target);
String msg = id.map(v -> " for ID '" + v + "': " + exMsg).orElse(": " + exMsg + ": for target '" + target.toString() + "'");
if (ex instanceof MarketDataNotFoundException) {
failure = Result.failure(
FailureReason.MISSING_DATA,
ex,
"Missing market data when invoking function '{}'{}",
fnName,
msg);
} else if (ex instanceof ReferenceDataNotFoundException) {
failure = Result.failure(
FailureReason.MISSING_DATA,
ex,
"Missing reference data when invoking function '{}'{}",
fnName,
msg);
} else if (ex instanceof UnsupportedOperationException) {
failure = Result.failure(
FailureReason.UNSUPPORTED,
ex,
"Unsupported operation when invoking function '{}'{}",
fnName,
msg);
} else {
failure = Result.failure(
FailureReason.CALCULATION_FAILED,
ex,
"Error when invoking function '{}'{}",
fnName,
msg);
}
return getMeasures().stream().collect(toImmutableMap(m -> m, m -> failure));
}
//-------------------------------------------------------------------------
@Override
public String toString() {
return "CalculationTask" + cells;
}
//------------------------- AUTOGENERATED START -------------------------
/**
* The meta-bean for {@code CalculationTask}.
*/
private static final TypedMetaBean META_BEAN =
LightMetaBean.of(
CalculationTask.class,
MethodHandles.lookup(),
new String[] {
"target",
"function",
"parameters",
"cells"},
null,
null,
null,
ImmutableList.of());
/**
* The meta-bean for {@code CalculationTask}.
* @return the meta-bean, not null
*/
public static TypedMetaBean meta() {
return META_BEAN;
}
static {
MetaBean.register(META_BEAN);
}
private CalculationTask(
CalculationTarget target,
CalculationFunction function,
CalculationParameters parameters,
List cells) {
JodaBeanUtils.notNull(target, "target");
JodaBeanUtils.notNull(function, "function");
JodaBeanUtils.notNull(parameters, "parameters");
JodaBeanUtils.notEmpty(cells, "cells");
this.target = target;
this.function = function;
this.parameters = parameters;
this.cells = ImmutableList.copyOf(cells);
}
@Override
public TypedMetaBean metaBean() {
return META_BEAN;
}
//-----------------------------------------------------------------------
/**
* Gets the target for which the value will be calculated.
* This is typically a trade.
* @return the value of the property, not null
*/
public CalculationTarget getTarget() {
return target;
}
//-----------------------------------------------------------------------
/**
* Gets the function that will calculate the value.
* @return the value of the property, not null
*/
public CalculationFunction getFunction() {
return function;
}
//-----------------------------------------------------------------------
/**
* Gets the additional parameters.
* @return the value of the property, not null
*/
public CalculationParameters getParameters() {
return parameters;
}
//-----------------------------------------------------------------------
/**
* Gets the cells to be calculated.
* @return the value of the property, not empty
*/
public List getCells() {
return cells;
}
//-----------------------------------------------------------------------
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj.getClass() == this.getClass()) {
CalculationTask other = (CalculationTask) obj;
return JodaBeanUtils.equal(target, other.target) &&
JodaBeanUtils.equal(function, other.function) &&
JodaBeanUtils.equal(parameters, other.parameters) &&
JodaBeanUtils.equal(cells, other.cells);
}
return false;
}
@Override
public int hashCode() {
int hash = getClass().hashCode();
hash = hash * 31 + JodaBeanUtils.hashCode(target);
hash = hash * 31 + JodaBeanUtils.hashCode(function);
hash = hash * 31 + JodaBeanUtils.hashCode(parameters);
hash = hash * 31 + JodaBeanUtils.hashCode(cells);
return hash;
}
//-------------------------- AUTOGENERATED END --------------------------
}