com.opengamma.strata.calc.marketdata.scenario.ScenarioDefinition.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.calc.marketdata.scenario.ScenarioDefinition.java

Source

/**
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.calc.marketdata.scenario;

import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.IntStream;

import org.joda.beans.Bean;
import org.joda.beans.BeanDefinition;
import org.joda.beans.ImmutableBean;
import org.joda.beans.ImmutableValidator;
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.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.MapStream;

/**
 * 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>
 * Each 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 = "notNull", builderType = "List<? extends PerturbationMapping<?>>")
    private final ImmutableList<PerturbationMapping<?>> mappings;

    /** The names of the scenarios. */
    @PropertyDefinition(validate = "notNull")
    private final ImmutableList<String> scenarioNames;

    /** An empty scenario definition. */
    private static final ScenarioDefinition EMPTY = ScenarioDefinition.builder().build();

    /**
     * Returns an empty scenario definition.
     *
     * @return an empty scenario definition
     */
    public static ScenarioDefinition empty() {
        return EMPTY;
    }

    /**
     * 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 mapping  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<?>> mapping) {
        ArgChecker.notEmpty(mapping, "mappings");

        int numScenarios = countScenarios(mapping, false);

        for (int i = 1; i < mapping.size(); i++) {
            if (mapping.get(i).getScenarioCount() != numScenarios) {
                throw new IllegalArgumentException(
                        "All mappings must have the same number of perturbations. First mapping" + " has "
                                + numScenarios + " perturbations, mapping " + i + " has "
                                + mapping.get(i).getScenarioCount());
            }
        }
        return new ScenarioDefinition(createMappings(mapping, false), generateNames(numScenarios));
    }

    /**
     * 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(PerturbationMapping<?>... mappings) {
        return ofMappings(Arrays.asList(mappings));
    }

    /**
     * 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 and the names must be unique
     * @return a scenario definition containing the perturbations in the mappings
     * @throws IllegalArgumentException if there are any duplicate scenario names
     */
    public static ScenarioDefinition ofMappings(List<? extends PerturbationMapping<?>> mappings,
            List<String> scenarioNames) {

        ArgChecker.notNull(scenarioNames, "scenarioNames");

        int numScenarios = scenarioNames.size();

        for (int i = 0; i < mappings.size(); i++) {
            if (mappings.get(i).getScenarioCount() != numScenarios) {
                throw new IllegalArgumentException(
                        "Each mapping must contain the same number of scenarios as the definition. There are "
                                + numScenarios + " scenarios in the definition, mapping " + i + " has "
                                + mappings.get(i).getScenarioCount() + " scenarios.");
            }
        }
        return new ScenarioDefinition(createMappings(mappings, false), scenarioNames);
    }

    /**
     * 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) {
        ArgChecker.notEmpty(mappings, "mappings");

        if (allCombinations) {
            return mappings.stream().mapToInt(PerturbationMapping::getScenarioCount).reduce(1, (s1, s2) -> s1 * s2);
        } else {
            return mappings.get(0).getScenarioCount();
        }
    }

    /**
     * Returns a definition for each scenario.
     *
     * @param mappings  the filters and perturbations that define the scenarios
     * @return the perturbations that should be applied in each scenario
     */
    private static List<? extends PerturbationMapping<?>> createMappings(
            List<? extends PerturbationMapping<?>> mappings, boolean allCombinations) {

        if (allCombinations) {
            // TODO Tidy up and remove references to allCombinations
            throw new UnsupportedOperationException();
        } else {
            return mappings;
        }
    }

    /**
     * Returns a list created by repeating the items in the input list multiple times, with each item repeated
     * in groups.
     * <p>
     * For example, given a list [1, 2, 3, 4], total count 12, group size 3 the result is
     * [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4].
     * <p>
     * This is used when creating scenarios from every possible combination of a set of perturbations.
     *
     * @param inputs  an input list whose elements are repeated in the output
     * @param totalCount  the number of elements in the output list
     * @param groupSize  the number of times each element should be repeated in each group
     * @param <T>  the type of the elements
     * @return a list created by repeating the elements of the input list
     */
    static <T> List<T> repeatItems(List<T> inputs, int totalCount, int groupSize) {
        ImmutableList.Builder<T> builder = ImmutableList.builder();

        for (int i = 0; i < (totalCount / groupSize / inputs.size()); i++) {
            for (T input : inputs) {
                builder.addAll(Collections.nCopies(groupSize, input));
            }
        }
        return builder.build();
    }

    /**
     * Generates simple names for the scenarios of the form 'Scenario 1' etc.
     */
    private static ImmutableList<String> generateNames(int numScenarios) {
        return IntStream.range(1, numScenarios + 1).mapToObj(i -> "Scenario " + i).collect(toImmutableList());
    }

    // validates that there are no duplicate scenario names
    @ImmutableValidator
    private void validate() {
        Map<String, List<String>> nameMap = scenarioNames.stream().collect(groupingBy(name -> name));
        List<String> duplicateNames = MapStream.of(nameMap).filterValues(names -> names.size() > 1)
                .map((name, names) -> name).collect(toImmutableList());

        if (!duplicateNames.isEmpty()) {
            String duplicates = duplicateNames.stream().collect(joining(", "));
            throw new IllegalArgumentException(
                    "Scenario names must be unique but duplicates were found: " + duplicates);
        }
    }

    /**
     * Returns the number of scenarios.
     *
     * @return the number of scenarios
     */
    public int getScenarioCount() {
        return scenarioNames.size();
    }

    //------------------------- 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<? extends PerturbationMapping<?>> mappings, List<String> scenarioNames) {
        JodaBeanUtils.notNull(mappings, "mappings");
        JodaBeanUtils.notNull(scenarioNames, "scenarioNames");
        this.mappings = ImmutableList.copyOf(mappings);
        this.scenarioNames = ImmutableList.copyOf(scenarioNames);
        validate();
    }

    @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 null
     */
    public ImmutableList<PerturbationMapping<?>> getMappings() {
        return mappings;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the names of the scenarios.
     * @return the value of the property, not null
     */
    public ImmutableList<String> getScenarioNames() {
        return scenarioNames;
    }

    //-----------------------------------------------------------------------
    /**
     * 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(mappings, other.mappings)
                    && JodaBeanUtils.equal(scenarioNames, other.scenarioNames);
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = getClass().hashCode();
        hash = hash * 31 + JodaBeanUtils.hashCode(mappings);
        hash = hash * 31 + JodaBeanUtils.hashCode(scenarioNames);
        return hash;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(96);
        buf.append("ScenarioDefinition{");
        buf.append("mappings").append('=').append(mappings).append(',').append(' ');
        buf.append("scenarioNames").append('=').append(JodaBeanUtils.toString(scenarioNames));
        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 mappings} property.
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        private final MetaProperty<ImmutableList<PerturbationMapping<?>>> mappings = DirectMetaProperty
                .ofImmutable(this, "mappings", ScenarioDefinition.class, (Class) ImmutableList.class);
        /**
         * The meta-property for the {@code scenarioNames} property.
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        private final MetaProperty<ImmutableList<String>> scenarioNames = DirectMetaProperty.ofImmutable(this,
                "scenarioNames", ScenarioDefinition.class, (Class) ImmutableList.class);
        /**
         * The meta-properties.
         */
        private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(this, null,
                "mappings", "scenarioNames");

        /**
         * Restricted constructor.
         */
        private Meta() {
        }

        @Override
        protected MetaProperty<?> metaPropertyGet(String propertyName) {
            switch (propertyName.hashCode()) {
            case 194445669: // mappings
                return mappings;
            case -1193464424: // scenarioNames
                return scenarioNames;
            }
            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 mappings} property.
         * @return the meta-property, not null
         */
        public MetaProperty<ImmutableList<PerturbationMapping<?>>> mappings() {
            return mappings;
        }

        /**
         * The meta-property for the {@code scenarioNames} property.
         * @return the meta-property, not null
         */
        public MetaProperty<ImmutableList<String>> scenarioNames() {
            return scenarioNames;
        }

        //-----------------------------------------------------------------------
        @Override
        protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
            switch (propertyName.hashCode()) {
            case 194445669: // mappings
                return ((ScenarioDefinition) bean).getMappings();
            case -1193464424: // scenarioNames
                return ((ScenarioDefinition) bean).getScenarioNames();
            }
            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<? extends PerturbationMapping<?>> mappings = ImmutableList.of();
        private List<String> scenarioNames = ImmutableList.of();

        /**
         * Restricted constructor.
         */
        private Builder() {
        }

        /**
         * Restricted copy constructor.
         * @param beanToCopy  the bean to copy from, not null
         */
        private Builder(ScenarioDefinition beanToCopy) {
            this.mappings = beanToCopy.getMappings();
            this.scenarioNames = beanToCopy.getScenarioNames();
        }

        //-----------------------------------------------------------------------
        @Override
        public Object get(String propertyName) {
            switch (propertyName.hashCode()) {
            case 194445669: // mappings
                return mappings;
            case -1193464424: // scenarioNames
                return scenarioNames;
            default:
                throw new NoSuchElementException("Unknown property: " + propertyName);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public Builder set(String propertyName, Object newValue) {
            switch (propertyName.hashCode()) {
            case 194445669: // mappings
                this.mappings = (List<? extends PerturbationMapping<?>>) newValue;
                break;
            case -1193464424: // scenarioNames
                this.scenarioNames = (List<String>) 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(mappings, scenarioNames);
        }

        //-----------------------------------------------------------------------
        /**
         * Sets the market data filters and perturbations that define the scenarios.
         * @param mappings  the new value, not null
         * @return this, for chaining, not null
         */
        public Builder mappings(List<? extends PerturbationMapping<?>> mappings) {
            JodaBeanUtils.notNull(mappings, "mappings");
            this.mappings = mappings;
            return this;
        }

        /**
         * Sets the {@code mappings} property in the builder
         * from an array of objects.
         * @param mappings  the new value, not null
         * @return this, for chaining, not null
         */
        public Builder mappings(PerturbationMapping<?>... mappings) {
            return mappings(ImmutableList.copyOf(mappings));
        }

        /**
         * Sets the names of the scenarios.
         * @param scenarioNames  the new value, not null
         * @return this, for chaining, not null
         */
        public Builder scenarioNames(List<String> scenarioNames) {
            JodaBeanUtils.notNull(scenarioNames, "scenarioNames");
            this.scenarioNames = scenarioNames;
            return this;
        }

        /**
         * Sets the {@code scenarioNames} property in the builder
         * from an array of objects.
         * @param scenarioNames  the new value, not null
         * @return this, for chaining, not null
         */
        public Builder scenarioNames(String... scenarioNames) {
            return scenarioNames(ImmutableList.copyOf(scenarioNames));
        }

        //-----------------------------------------------------------------------
        @Override
        public String toString() {
            StringBuilder buf = new StringBuilder(96);
            buf.append("ScenarioDefinition.Builder{");
            buf.append("mappings").append('=').append(JodaBeanUtils.toString(mappings)).append(',').append(' ');
            buf.append("scenarioNames").append('=').append(JodaBeanUtils.toString(scenarioNames));
            buf.append('}');
            return buf.toString();
        }

    }

    ///CLOVER:ON
    //-------------------------- AUTOGENERATED END --------------------------
}