/**
* Copyright (C) 2016 - 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.basics.currency.Currency.AUD;
import static com.opengamma.strata.calc.TestingMeasures.BUCKETED_PV01;
import static com.opengamma.strata.calc.TestingMeasures.CASH_FLOWS;
import static com.opengamma.strata.calc.TestingMeasures.PAR_RATE;
import static com.opengamma.strata.calc.TestingMeasures.PRESENT_VALUE;
import static com.opengamma.strata.calc.TestingMeasures.PRESENT_VALUE_MULTI_CCY;
import static com.opengamma.strata.collect.CollectProjectAssertions.assertThat;
import static java.util.stream.Collectors.toMap;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.opengamma.strata.basics.CalculationTarget;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.calc.Measure;
import com.opengamma.strata.calc.marketdata.TestObservableId;
import com.opengamma.strata.collect.MapStream;
import com.opengamma.strata.collect.result.FailureReason;
import com.opengamma.strata.collect.result.Result;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
@Test
public class DerivedCalculationFunctionTest {
/**
* Tests all measures are calculated by the derived function and the underlying function.
*/
public void calculateMeasure() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.success(3),
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01, CASH_FLOWS, PAR_RATE, PRESENT_VALUE);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(wrapper.supportedMeasures()).isEqualTo(measures);
assertThat(wrapper.targetType()).isEqualTo(TestTarget.class);
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasValue(35);
assertThat(results.get(CASH_FLOWS)).hasValue(3);
assertThat(results.get(PAR_RATE)).hasValue(5);
assertThat(results.get(PRESENT_VALUE)).hasValue(7);
}
/**
* Test two derived function composed together
*/
public void calculateMeasuresNestedDerivedClasses() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.success(3),
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DerivedFn derivedFn1 = new DerivedFn(BUCKETED_PV01);
DerivedFn derivedFn2 = new DerivedFn(PRESENT_VALUE_MULTI_CCY);
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
derivedFn1,
new DelegateFn(delegateResults));
wrapper = new DerivedCalculationFunctionWrapper<>(derivedFn2, wrapper);
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01, PRESENT_VALUE_MULTI_CCY, CASH_FLOWS, PAR_RATE, PRESENT_VALUE);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(wrapper.supportedMeasures()).isEqualTo(measures);
assertThat(wrapper.targetType()).isEqualTo(TestTarget.class);
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasValue(35);
assertThat(results.get(PRESENT_VALUE_MULTI_CCY)).hasValue(35);
assertThat(results.get(CASH_FLOWS)).hasValue(3);
assertThat(results.get(PAR_RATE)).hasValue(5);
assertThat(results.get(PRESENT_VALUE)).hasValue(7);
}
/**
* Test that the derived measure isn't calculated unless it is requested.
*/
public void derivedMeasureNotRequested() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.success(3),
PRESENT_VALUE, Result.success(7));
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
Set<Measure> measures = ImmutableSet.of(CASH_FLOWS, PRESENT_VALUE);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(CASH_FLOWS)).hasValue(3);
assertThat(results.get(PRESENT_VALUE)).hasValue(7);
}
/**
* Test that measures aren't returned if they are needed to calculate the derived measure but aren't
* requested by the user.
*/
public void requiredMeasureNotReturned() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.success(3),
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasValue(35);
}
/**
* Test the behaviour when the underlying function doesn't support the measures required by the derived function.
*/
public void requiredMeasuresNotSupported() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01, PAR_RATE);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
// The derived measure isn't supported because its required measure isn't available
assertThat(wrapper.supportedMeasures()).isEqualTo(ImmutableSet.of(PAR_RATE, PRESENT_VALUE));
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasFailureMessageMatching(".*cannot calculate the required measures.*");
assertThat(results.get(PAR_RATE)).hasValue(5);
}
/**
* Test the derived measure result is a failure if any of the required measures are failures
*/
public void requiredMeasureFails() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.failure(FailureReason.ERROR, "Failed to calculate bar"),
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasFailureMessageMatching("Failed to calculate bar");
}
/**
* Test the behaviour when the delegate function returns no value for a measure it claims to support.
* This is a bug in the function, it should always return a result for all measures that are supported and
* were requested.
*/
public void supportedMeasureNotReturned() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of(
CASH_FLOWS, Result.success(3),
PAR_RATE, Result.success(5),
PRESENT_VALUE, Result.success(7));
DelegateFn delegateFn = new DelegateFn(delegateResults) {
@Override
public Map<Measure, Result<?>> calculate(
TestTarget target,
Set<Measure> measures,
CalculationParameters parameters,
ScenarioMarketData marketData,
ReferenceData refData) {
// Don't return TestingMeasures.CASH_FLOWS even though it should be supported
return ImmutableMap.of(PAR_RATE, Result.success(5));
}
};
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
delegateFn);
Set<Measure> measures = ImmutableSet.of(BUCKETED_PV01);
Map<Measure, Result<?>> results = wrapper.calculate(
target,
measures,
CalculationParameters.empty(),
ScenarioMarketData.empty(),
ReferenceData.standard());
assertThat(results.keySet()).isEqualTo(measures);
assertThat(results.get(BUCKETED_PV01)).hasFailureMessageMatching(".*did not return the expected measures.*");
}
public void requirements() {
TestTarget target = new TestTarget(10);
Map<Measure, Result<?>> delegateResults = ImmutableMap.of();
DerivedCalculationFunctionWrapper<TestTarget, Integer> wrapper = new DerivedCalculationFunctionWrapper<>(
new DerivedFn(),
new DelegateFn(delegateResults));
FunctionRequirements requirements = wrapper.requirements(
target,
ImmutableSet.of(),
CalculationParameters.empty(),
ReferenceData.empty());
FunctionRequirements expected = FunctionRequirements.builder()
.valueRequirements(TestObservableId.of("a"), TestObservableId.of("b"), TestObservableId.of("d"))
.timeSeriesRequirements(TestObservableId.of("c"), TestObservableId.of("e"))
.outputCurrencies(Currency.GBP, Currency.EUR, Currency.USD)
.build();
assertThat(requirements).isEqualTo(expected);
assertThat(wrapper.naturalCurrency(target, ReferenceData.empty())).isEqualTo(Currency.AUD);
}
}
//--------------------------------------------------------------------------------------------------
final class TestTarget implements CalculationTarget {
private final int value;
TestTarget(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
final class DerivedFn extends AbstractDerivedCalculationFunction<TestTarget, Integer> {
DerivedFn(Measure measure, Set<Measure> requiredMeasures) {
super(TestTarget.class, measure, requiredMeasures);
}
DerivedFn(Measure measure) {
this(measure, ImmutableSet.of(CASH_FLOWS, PAR_RATE));
}
DerivedFn() {
this(BUCKETED_PV01);
}
@Override
public Integer calculate(
TestTarget target,
Map<Measure, Object> requiredMeasures,
CalculationParameters parameters,
ScenarioMarketData marketData,
ReferenceData refData) {
Integer bar = (Integer) requiredMeasures.get(CASH_FLOWS);
Integer baz = (Integer) requiredMeasures.get(PAR_RATE);
return target.getValue() * bar + baz;
}
@Override
public FunctionRequirements requirements(TestTarget target, CalculationParameters parameters, ReferenceData refData) {
return FunctionRequirements.builder()
.valueRequirements(TestObservableId.of("a"), TestObservableId.of("b"))
.timeSeriesRequirements(TestObservableId.of("c"))
.outputCurrencies(Currency.GBP)
.build();
}
}
//--------------------------------------------------------------------------------------------------
class DelegateFn implements CalculationFunction<TestTarget> {
private final Map<Measure, Result<?>> results;
DelegateFn(Map<Measure, Result<?>> results) {
this.results = results;
}
@Override
public Class<TestTarget> targetType() {
return TestTarget.class;
}
@Override
public Set<Measure> supportedMeasures() {
return results.keySet();
}
@Override
public Currency naturalCurrency(TestTarget target, ReferenceData refData) {
return AUD;
}
@Override
public FunctionRequirements requirements(
TestTarget target,
Set<Measure> measures,
CalculationParameters parameters,
ReferenceData refData) {
return FunctionRequirements.builder()
.valueRequirements(TestObservableId.of("d"))
.timeSeriesRequirements(TestObservableId.of("e"))
.outputCurrencies(Currency.EUR, Currency.USD)
.build();
}
@Override
public Map<Measure, Result<?>> calculate(
TestTarget target,
Set<Measure> measures,
CalculationParameters parameters,
ScenarioMarketData marketData,
ReferenceData refData) {
Set<Measure> missingMeasures = Sets.difference(measures, results.keySet());
Map<Measure, Result<?>> results = MapStream.of(this.results).filterKeys(measures::contains).toMap();
Map<Measure, Result<?>> missingResults = missingMeasures.stream().collect(toMap(m -> m, this::missingResult));
Map<Measure, Result<?>> allResults = new HashMap<>(results);
allResults.putAll(missingResults);
return allResults;
}
private Result<?> missingResult(Measure measure) {
return Result.failure(FailureReason.CALCULATION_FAILED, "{} not supported", measure);
}
}