/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame.marketdata.scenarios; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.joda.beans.Bean; import org.joda.beans.BeanDefinition; import org.joda.beans.ImmutableBean; import org.joda.beans.JodaBeanUtils; import org.joda.beans.MetaProperty; import org.joda.beans.Property; import org.joda.beans.PropertyDefinition; import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; import org.joda.beans.impl.direct.DirectMetaBean; import org.joda.beans.impl.direct.DirectMetaProperty; import org.joda.beans.impl.direct.DirectMetaPropertyMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.opengamma.sesame.marketdata.MarketDataUtils; import com.opengamma.util.ArgumentChecker; /** * A scenario definition defines how to create multiple sets of market data for running calculations over * a set of scenarios. The scenario data is created by applying perturbations to a set of base market data. * A different set of perturbations is used for each scenario. * <p> * A definition contains multiple {@link SingleScenarioDefinition} instances, each corresponding to one * scenario. Each single scenario definition contains market data filters and perturbations. Filters * are used to choose items of market data that are shocked in the scenario, and the perturbations * define those shocks. * <p> * Perturbations are applied in the order they are defined in scenario. An item of market data * can only be perturbed once, so if multiple mappings apply to it, only the first will be used. */ @BeanDefinition public final class ScenarioDefinition implements ImmutableBean { /** The market data filters and perturbations that define the scenarios. */ @PropertyDefinition(validate = "notEmpty") private final ImmutableList<SingleScenarioDefinition> _scenarios; /** * Creates a scenario definition from a list of individual scenarios. * * @param scenarios the scenarios that make up the scenario definition * @return a scenario definition containing the scenarios */ public static ScenarioDefinition ofScenarios(List<SingleScenarioDefinition> scenarios) { return new ScenarioDefinition(scenarios); } /** * Creates a scenario definition from individual scenarios. * * @param scenarios the scenarios that make up the scenario definition * @return a scenario definition containing the scenarios */ public static ScenarioDefinition ofScenarios(SingleScenarioDefinition... scenarios) { return new ScenarioDefinition(ImmutableList.copyOf(scenarios)); } /** * Returns a scenario definition containing the perturbations in {@code mappings}. * <p> * Each mapping must contain the same number of perturbations. The definition will contain the * same number of scenarios as the number of perturbations in each mapping. * <p> * The first scenario contains the first perturbation from each mapping, the second scenario contains * the second perturbation from each mapping, and so on. * <p> * Given three mappings, A, B and C, each containing two perturbations, 1 and 2, there will be two * scenarios generated: * <pre> * | | A | B | C | * |------------|------|------|------| * | Scenario 1 | A[1] | B[1] | C[1] | * | Scenario 2 | A[2] | B[2] | C[2] | * </pre> * For example, consider the following perturbation mappings: * <ul> * <li>Filter: USD Curves, Shocks: [-10bp, 0, +10bp]</li> * <li>Filter: EUR/USD Rate, Shocks: [+5%, 0, -5%]</li> * </ul> * The scenario definition would contain the following three scenarios: * <pre> * | | USD Curves | EUR/USD Rate | * |------------|------------|--------------| * | Scenario 1 | -10bp | +5% | * | Scenario 2 | 0 | 0 | * | Scenario 3 | +10bp | -5% | * </pre> * * @param mappings the filters and perturbations that define the scenario. Each mapping must contain the same * number of perturbations * @return a scenario definition containing the perturbations in the mappings */ public static ScenarioDefinition ofMappings(List<? extends PerturbationMapping<?>> mappings) { ArgumentChecker.notEmpty(mappings, "mappings"); int numScenarios = countScenarios(mappings, false); for (int i = 1; i < mappings.size(); i++) { if (mappings.get(i).getPerturbations().size() != numScenarios) { throw new IllegalArgumentException( "All mappings must have the same number of perturbations. First mapping" + " has " + numScenarios + " perturbations, mapping " + i + " has " + mappings.get(i).getPerturbations().size()); } } ImmutableSet<String> scenarioNames = generateNames(numScenarios); return new ScenarioDefinition(createScenarios(scenarioNames, mappings, false)); } /** * Returns a scenario definition containing the perturbations in {@code mappings}. * <p> * Each mapping must contain the same number of perturbations. The definition will contain the * same number of scenarios as the number of perturbations in each mapping. * <p> * The first scenario contains the first perturbation from each mapping, the second scenario contains * the second perturbation from each mapping, and so on. * <p> * The set of scenario names must contain the same number of elements as the mappings. * <p> * Given three mappings, A, B and C, each containing two perturbations, 1 and 2, there will be two * scenarios generated: * <pre> * | | A | B | C | * |------------|------|------|------| * | Scenario 1 | A[1] | B[1] | C[1] | * | Scenario 2 | A[2] | B[2] | C[2] | * </pre> * For example, consider the following perturbation mappings: * <ul> * <li>Filter: USD Curves, Shocks: [-10bp, 0, +10bp]</li> * <li>Filter: EUR/USD Rate, Shocks: [+5%, 0, -5%]</li> * </ul> * The scenario definition would contain the following three scenarios: * <pre> * | | USD Curves | EUR/USD Rate | * |------------|------------|--------------| * | Scenario 1 | -10bp | +5% | * | Scenario 2 | 0 | 0 | * | Scenario 3 | +10bp | -5% | * * @param mappings the filters and perturbations that define the scenario. Each mapping must contain the same * number of perturbations * @param scenarioNames the names of the scenarios. This must be the same size as the list of perturbations * in each mapping * @return a scenario definition containing the perturbations in the mappings */ public static ScenarioDefinition ofMappings( ImmutableSet<String> scenarioNames, List<? extends PerturbationMapping<?>> mappings) { ArgumentChecker.notNull(scenarioNames, "scenarioNames"); int numScenarios = scenarioNames.size(); for (int i = 0; i < mappings.size(); i++) { if (mappings.get(i).getPerturbations().size() != numScenarios) { throw new IllegalArgumentException( "Each mapping must contain the same number of perturbations as there are scenarios. There are " + numScenarios + " scenarios, mapping " + i + " has " + mappings.get(i).getPerturbations().size() + " perturbations."); } } return new ScenarioDefinition(createScenarios(scenarioNames, mappings, false)); } /** * Returns a scenario definition created from all possible combinations of the mappings. * <p> * The mappings can have any number of perturbations, they do not need to have the same number as each other. * Each scenario contain one perturbation from each mapping. One scenario is created for each * possible combination of perturbations formed by taking one from each mapping. * <p> * The number of scenarios in the definition will be equal to the product of the number of perturbations * in the mappings. * <p> * Given three mappings, A, B and C, each containing two perturbations, 1 and 2, there will be eight * scenarios generated: * <pre> * | | A | B | C | * |------------|------|------|------| * | Scenario 1 | A[1] | B[1] | C[1] | * | Scenario 2 | A[1] | B[1] | C[2] | * | Scenario 3 | A[1] | B[2] | C[1] | * | Scenario 4 | A[1] | B[2] | C[2] | * | Scenario 5 | A[2] | B[1] | C[1] | * | Scenario 6 | A[2] | B[1] | C[2] | * | Scenario 7 | A[2] | B[2] | C[1] | * | Scenario 8 | A[2] | B[2] | C[2] | * </pre> * For example, consider the following perturbation mappings: * <ul> * <li>Filter: USD Curves, Shocks: [-10bp, 0, +10bp]</li> * <li>Filter: EUR/USD Rate, Shocks: [+5%, 0, -5%]</li> * </ul> * The scenario definition would contain the following nine scenarios: * <pre> * | | USD Curves | EUR/USD Rate | * |------------|------------|--------------| * | Scenario 1 | -10bp | +5% | * | Scenario 2 | -10bp | 0 | * | Scenario 3 | -10bp | -5% | * | Scenario 4 | 0 | +5% | * | Scenario 5 | 0 | 0 | * | Scenario 6 | 0 | -5% | * | Scenario 7 | +10bp | +5% | * | Scenario 8 | +10bp | 0 | * | Scenario 9 | +10bp | -5% | * * @param mappings the filters and perturbations that define the scenarios. They can contain any number * of perturbations, and they do not need to have the same number of perturbations * @return a scenario definition containing the perturbations in the mappings */ public static ScenarioDefinition ofAllCombinations(List<? extends PerturbationMapping<?>> mappings) { int numScenarios = countScenarios(mappings, true); ImmutableSet<String> scenarioNames = generateNames(numScenarios); return new ScenarioDefinition(createScenarios(scenarioNames, mappings, true)); } /** * Returns a scenario definition created from all possible combinations of the mappings. * <p> * The mappings can have any number of perturbations, they do not need to have the same number as each other. * Each scenario contain one perturbation from each mapping. One scenario is created for each * possible combination of perturbations formed by taking one from each mapping. * <p> * The number of scenarios in the definition will be equal to the product of the number of perturbations * in the mappings. * <p> * Given three mappings, A, B and C, each containing two perturbations, 1 and 2, there will be eight * scenarios generated: * <pre> * | | A | B | C | * |------------|------|------|------| * | Scenario 1 | A[1] | B[1] | C[1] | * | Scenario 2 | A[1] | B[1] | C[2] | * | Scenario 3 | A[1] | B[2] | C[1] | * | Scenario 4 | A[1] | B[2] | C[2] | * | Scenario 5 | A[2] | B[1] | C[1] | * | Scenario 6 | A[2] | B[1] | C[2] | * | Scenario 7 | A[2] | B[2] | C[1] | * | Scenario 8 | A[2] | B[2] | C[2] | * </pre> * For example, consider the following perturbation mappings: * <ul> * <li>Filter: USD Curves, Shocks: [-10bp, 0, +10bp]</li> * <li>Filter: EUR/USD Rate, Shocks: [+5%, 0, -5%]</li> * </ul> * The scenario definition would contain the following nine scenarios: * <pre> * | | USD Curves | EUR/USD Rate | * |------------|------------|--------------| * | Scenario 1 | -10bp | +5% | * | Scenario 2 | -10bp | 0 | * | Scenario 3 | -10bp | -5% | * | Scenario 4 | 0 | +5% | * | Scenario 5 | 0 | 0 | * | Scenario 6 | 0 | -5% | * | Scenario 7 | +10bp | +5% | * | Scenario 8 | +10bp | 0 | * | Scenario 9 | +10bp | -5% | * * @param mappings the filters and perturbations that define the scenarios. They can contain any number * of perturbations, and they do not need to have the same number of perturbations * @return a scenario definition containing the perturbations in the mappings */ public static ScenarioDefinition ofAllCombinations( ImmutableSet<String> scenarioNames, List<? extends PerturbationMapping<?>> mappings) { ArgumentChecker.notEmpty(scenarioNames, "scenarioNames"); ArgumentChecker.notEmpty(mappings, "mappings"); int numScenarios = countScenarios(mappings, true); if (numScenarios != scenarioNames.size()) { throw new IllegalArgumentException( "The number of scenario names provided is " + scenarioNames.size() + " but " + "the number of scenarios is " + numScenarios); } return new ScenarioDefinition(createScenarios(scenarioNames, mappings, true)); } /** * Counts the number of scenarios implied by the mappings and the {@code allCombinations} flag. * * @param mappings the mappings that make up the scenarios * @param allCombinations whether the scenarios are generated by taking all combinations of perturbations * formed by taking one from each mapping * @return the number of scenarios */ private static int countScenarios(List<? extends PerturbationMapping<?>> mappings, boolean allCombinations) { ArgumentChecker.notEmpty(mappings, "mappings"); if (allCombinations) { // This accumulates the number of scenarios int numScenarios = mappings.get(0).getPerturbations().size(); for (int i = 1; i < mappings.size(); i++) { numScenarios *= mappings.get(i).getPerturbations().size(); } return numScenarios; } else { return mappings.get(0).getPerturbations().size(); } } /** * Returns a definition for each scenario. * * @param scenarioNames the names of the scenarios * @param mappings the filters and perturbations that define the scenarios * @return the perturbations that should be applied in each scenario */ private static List<SingleScenarioDefinition> createScenarios( ImmutableSet<String> scenarioNames, List<? extends PerturbationMapping<?>> mappings, boolean allCombinations) { ImmutableList.Builder<List<SinglePerturbationMapping>> singleMappingsBuilder = ImmutableList.builder(); // Flatten the perturbation mappings into lists of single perturbations. // The mappings contain a filter and multiple perturbations used across multiple scenarios. // The single perturbation mappings contain a filter and a perturbation, and are used in a single scenario for (PerturbationMapping mapping : mappings) { singleMappingsBuilder.add(flattenPerturbations(mapping)); } List<List<SinglePerturbationMapping>> perturbations = singleMappingsBuilder.build(); List<String> scenarioNamesList = scenarioNames.asList(); if (allCombinations) { return createScenariosForAllCombinations(perturbations, scenarioNamesList); } else { return createScenariosForSimpleCombinations(perturbations, scenarioNamesList); } } /** * Creates definitions for each individual scenario. Each scenario is created by taking one * perturbation for each perturbation mapping. * <p> * For example, consider a scenario definition containing a perturbation mapping for a curve with five * perturbations and a perturbation mapping for an FX rate with five perturbations. The result would * be five scenarios. * <p> * The first scenario contains the first curve shock and the first FX shock, the second scenario contains * the second curve shock and second FX shock, and so on. * * @param perturbations the perturbations. The outer list contains an element for each perturbation mapping. * The inner list contains an element for each perturbation in the perturbation mapping. The inners lists * must have the same size * @param scenarioNames the names of the scenarios * @return definitions for the individual scenarios */ private static List<SingleScenarioDefinition> createScenariosForSimpleCombinations( List<List<SinglePerturbationMapping>> perturbations, List<String> scenarioNames) { ImmutableList.Builder<SingleScenarioDefinition> scenariosBuilder = ImmutableList.builder(); for (int i = 0; i < scenarioNames.size(); i++) { ImmutableList.Builder<SinglePerturbationMapping> scenarioBuilder = ImmutableList.builder(); for (List<SinglePerturbationMapping> mappingPerturbations : perturbations) { scenarioBuilder.add(mappingPerturbations.get(i)); } scenariosBuilder.add(SingleScenarioDefinition.of(scenarioNames.get(i), scenarioBuilder.build())); } return scenariosBuilder.build(); } /** * Creates definitions for each individual scenario. Each scenario contains one perturbation from each * perturbation mapping. A scenario is created for all possible combinations of perturbations. * * @param perturbations the perturbations. The outer list contains an element for each perturbation mapping. * The inner list contains an element for each perturbation in the perturbation mapping. The inners lists * do not have to have the same size * @param scenarioNames the names of the scenarios * @return definitions for the individual scenarios */ private static List<SingleScenarioDefinition> createScenariosForAllCombinations( List<List<SinglePerturbationMapping>> perturbations, List<String> scenarioNames) { List<List<SinglePerturbationMapping>> cartesianProduct = MarketDataUtils.cartesianProduct(perturbations); ImmutableList.Builder<SingleScenarioDefinition> scenariosBuilder = ImmutableList.builder(); // Each of the inner lists corresponds to a single scenario for (int i = 0; i < scenarioNames.size(); i++) { scenariosBuilder.add(SingleScenarioDefinition.of(scenarioNames.get(i), cartesianProduct.get(i))); } return scenariosBuilder.build(); } /** * Generates simple names for the scenarios of the form 'Scenario 1' etc. */ private static ImmutableSet<String> generateNames(int numScenarios) { ImmutableSet.Builder<String> namesBuilder = ImmutableSet.builder(); for (int i = 1; i <= numScenarios; i++) { namesBuilder.add("Scenario " + Integer.toString(i)); } return namesBuilder.build(); } /** * A {@link PerturbationMapping} contains one filter and multiple perturbations. This method converts a * {@code PerturbationMapping} into a list of {@link SinglePerturbationMapping} which are a pair of filter * and perturbation. * <p> * The input data is: * <pre> * [mapping, [perturbation1, perturbation2, perturbation3, ...]] * </pre> * and the output data is: * <pre> * [[mapping, perturbation1], [mapping, perturbation2], [mapping, perturbation3], ...] * </pre> * * @param mapping a perturbation mapping * @return a list in which each element contains the filter from {@code mapping} and a perturbation */ private static List<SinglePerturbationMapping> flattenPerturbations(PerturbationMapping<?> mapping) { ImmutableList.Builder<SinglePerturbationMapping> builder = ImmutableList.builder(); for (Perturbation perturbation : mapping.getPerturbations()) { SinglePerturbationMapping singleMapping = SinglePerturbationMapping.builder() .filter(mapping.getFilter()) .perturbation(perturbation) .build(); builder.add(singleMapping); } return builder.build(); } //------------------------- AUTOGENERATED START ------------------------- ///CLOVER:OFF /** * The meta-bean for {@code ScenarioDefinition}. * @return the meta-bean, not null */ public static ScenarioDefinition.Meta meta() { return ScenarioDefinition.Meta.INSTANCE; } static { JodaBeanUtils.registerMetaBean(ScenarioDefinition.Meta.INSTANCE); } /** * Returns a builder used to create an instance of the bean. * @return the builder, not null */ public static ScenarioDefinition.Builder builder() { return new ScenarioDefinition.Builder(); } private ScenarioDefinition( List<SingleScenarioDefinition> scenarios) { JodaBeanUtils.notEmpty(scenarios, "scenarios"); this._scenarios = ImmutableList.copyOf(scenarios); } @Override public ScenarioDefinition.Meta metaBean() { return ScenarioDefinition.Meta.INSTANCE; } @Override public <R> Property<R> property(String propertyName) { return metaBean().<R>metaProperty(propertyName).createProperty(this); } @Override public Set<String> propertyNames() { return metaBean().metaPropertyMap().keySet(); } //----------------------------------------------------------------------- /** * Gets the market data filters and perturbations that define the scenarios. * @return the value of the property, not empty */ public ImmutableList<SingleScenarioDefinition> getScenarios() { return _scenarios; } //----------------------------------------------------------------------- /** * Returns a builder that allows this bean to be mutated. * @return the mutable builder, not null */ public Builder toBuilder() { return new Builder(this); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { ScenarioDefinition other = (ScenarioDefinition) obj; return JodaBeanUtils.equal(getScenarios(), other.getScenarios()); } return false; } @Override public int hashCode() { int hash = getClass().hashCode(); hash = hash * 31 + JodaBeanUtils.hashCode(getScenarios()); return hash; } @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("ScenarioDefinition{"); buf.append("scenarios").append('=').append(JodaBeanUtils.toString(getScenarios())); buf.append('}'); return buf.toString(); } //----------------------------------------------------------------------- /** * The meta-bean for {@code ScenarioDefinition}. */ public static final class Meta extends DirectMetaBean { /** * The singleton instance of the meta-bean. */ static final Meta INSTANCE = new Meta(); /** * The meta-property for the {@code scenarios} property. */ @SuppressWarnings({"unchecked", "rawtypes" }) private final MetaProperty<ImmutableList<SingleScenarioDefinition>> _scenarios = DirectMetaProperty.ofImmutable( this, "scenarios", ScenarioDefinition.class, (Class) ImmutableList.class); /** * The meta-properties. */ private final Map<String, MetaProperty<?>> _metaPropertyMap$ = new DirectMetaPropertyMap( this, null, "scenarios"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty<?> metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case 1726545635: // scenarios return _scenarios; } return super.metaPropertyGet(propertyName); } @Override public ScenarioDefinition.Builder builder() { return new ScenarioDefinition.Builder(); } @Override public Class<? extends ScenarioDefinition> beanType() { return ScenarioDefinition.class; } @Override public Map<String, MetaProperty<?>> metaPropertyMap() { return _metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code scenarios} property. * @return the meta-property, not null */ public MetaProperty<ImmutableList<SingleScenarioDefinition>> scenarios() { return _scenarios; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case 1726545635: // scenarios return ((ScenarioDefinition) bean).getScenarios(); } return super.propertyGet(bean, propertyName, quiet); } @Override protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { metaProperty(propertyName); if (quiet) { return; } throw new UnsupportedOperationException("Property cannot be written: " + propertyName); } } //----------------------------------------------------------------------- /** * The bean-builder for {@code ScenarioDefinition}. */ public static final class Builder extends DirectFieldsBeanBuilder<ScenarioDefinition> { private List<SingleScenarioDefinition> _scenarios = new ArrayList<SingleScenarioDefinition>(); /** * Restricted constructor. */ private Builder() { } /** * Restricted copy constructor. * @param beanToCopy the bean to copy from, not null */ private Builder(ScenarioDefinition beanToCopy) { this._scenarios = new ArrayList<SingleScenarioDefinition>(beanToCopy.getScenarios()); } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case 1726545635: // scenarios return _scenarios; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @SuppressWarnings("unchecked") @Override public Builder set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case 1726545635: // scenarios this._scenarios = (List<SingleScenarioDefinition>) newValue; break; default: throw new NoSuchElementException("Unknown property: " + propertyName); } return this; } @Override public Builder set(MetaProperty<?> property, Object value) { super.set(property, value); return this; } @Override public Builder setString(String propertyName, String value) { setString(meta().metaProperty(propertyName), value); return this; } @Override public Builder setString(MetaProperty<?> property, String value) { super.setString(property, value); return this; } @Override public Builder setAll(Map<String, ? extends Object> propertyValueMap) { super.setAll(propertyValueMap); return this; } @Override public ScenarioDefinition build() { return new ScenarioDefinition( _scenarios); } //----------------------------------------------------------------------- /** * Sets the {@code scenarios} property in the builder. * @param scenarios the new value, not empty * @return this, for chaining, not null */ public Builder scenarios(List<SingleScenarioDefinition> scenarios) { JodaBeanUtils.notEmpty(scenarios, "scenarios"); this._scenarios = scenarios; return this; } /** * Sets the {@code scenarios} property in the builder * from an array of objects. * @param scenarios the new value, not empty * @return this, for chaining, not null */ public Builder scenarios(SingleScenarioDefinition... scenarios) { return scenarios(Arrays.asList(scenarios)); } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("ScenarioDefinition.Builder{"); buf.append("scenarios").append('=').append(JodaBeanUtils.toString(_scenarios)); buf.append('}'); return buf.toString(); } } ///CLOVER:ON //-------------------------- AUTOGENERATED END -------------------------- }