All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.market.param.CurrencyParameterSensitivities Maven / Gradle / Ivy

There is a newer version: 2.12.46
Show newest version
/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.market.param;

import static com.opengamma.strata.basics.currency.MultiCurrencyAmount.toMultiCurrencyAmount;
import static com.opengamma.strata.collect.Guavate.toImmutableList;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.gen.BeanDefinition;
import org.joda.beans.gen.ImmutableConstructor;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;
import org.joda.beans.impl.direct.DirectMetaPropertyMap;
import org.joda.beans.impl.direct.DirectPrivateBeanBuilder;

import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.FxConvertible;
import com.opengamma.strata.basics.currency.FxRateProvider;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.collect.Guavate;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.function.IntDoubleToDoubleFunction;
import com.opengamma.strata.data.MarketDataName;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.surface.Surface;

/**
 * Currency-based parameter sensitivity for parameterized market data, such as curves.
 * 

* Parameter sensitivity is the sensitivity of a value to the parameters of * {@linkplain ParameterizedData parameterized market data} objects that are used to determine the value. * Common {@code ParameterizedData} implementations include {@link Curve} and {@link Surface}. *

* For example, the parameter sensitivity for present value on a FRA might contain * two entries, one for the Ibor forward curve and one for the discount curve. * Each entry identifies the curve that was queried and the resulting sensitivity values, * one for each node on the curve. *

* The sensitivity is expressed as a single entry for piece of parameterized market data. * The sensitivity represents a monetary value in the specified currency. * The order of the list has no specific meaning. *

* One way of viewing this class is as a {@code Map} from a specific sensitivity key to * {@code DoubleArray} sensitivity values. However, instead of being structured as a {@code Map}, * the data is structured as a {@code List}, with the key and value in each entry. * As such, the sensitivities are always in a "normalized" form. */ @BeanDefinition(builderScope = "private") public final class CurrencyParameterSensitivities implements FxConvertible, ImmutableBean, Serializable { /** * An empty instance. */ private static final CurrencyParameterSensitivities EMPTY = new CurrencyParameterSensitivities(ImmutableList.of()); /** * The parameter sensitivities. *

* Each entry includes details of the {@link ParameterizedData} it relates to. */ @PropertyDefinition(validate = "notNull") private final ImmutableList sensitivities; //------------------------------------------------------------------------- /** * An empty sensitivity instance. * * @return the empty instance */ public static CurrencyParameterSensitivities empty() { return EMPTY; } /** * Returns a builder that can be used to create an instance of {@code CurrencyParameterSensitivities}. *

* The builder takes into account the parameter metadata when creating the sensitivity map. * As such, the parameter metadata added to the builder must not be empty. * * @return the builder */ public static CurrencyParameterSensitivitiesBuilder builder() { return new CurrencyParameterSensitivitiesBuilder(); } /** * Obtains an instance from a single sensitivity entry. * * @param sensitivity the sensitivity entry * @return the sensitivities instance */ public static CurrencyParameterSensitivities of(CurrencyParameterSensitivity sensitivity) { return new CurrencyParameterSensitivities(ImmutableList.of(sensitivity)); } /** * Obtains an instance from an array of sensitivity entries. *

* The sensitivities are sorted using {@link CurrencyParameterSensitivity#compareKey}. * * @param sensitivities the sensitivities * @return the sensitivities instance */ public static CurrencyParameterSensitivities of(CurrencyParameterSensitivity... sensitivities) { return of(Arrays.asList(sensitivities)); } /** * Obtains an instance from a list of sensitivity entries. *

* The sensitivities are sorted using {@link CurrencyParameterSensitivity#compareKey}. * * @param sensitivities the list of sensitivity entries * @return the sensitivities instance */ public static CurrencyParameterSensitivities of(List sensitivities) { List mutable = new ArrayList<>(); for (CurrencyParameterSensitivity otherSens : sensitivities) { insert(mutable, otherSens); } return new CurrencyParameterSensitivities(ImmutableList.copyOf(mutable)); } // used when not pre-sorted @ImmutableConstructor private CurrencyParameterSensitivities(List sensitivities) { if (sensitivities.size() < 2) { this.sensitivities = ImmutableList.copyOf(sensitivities); } else { List mutable = new ArrayList<>(sensitivities); mutable.sort(CurrencyParameterSensitivity::compareKey); this.sensitivities = ImmutableList.copyOf(mutable); } } // used when pre-sorted private CurrencyParameterSensitivities(ImmutableList sensitivities) { this.sensitivities = sensitivities; } //----------------------------------------------------------------------- /** * Gets the number of sensitivity entries. * * @return the size of the internal list of point sensitivities */ public int size() { return sensitivities.size(); } /** * Gets a single sensitivity instance by name and currency. * * @param name the curve name to find * @param currency the currency to find * @return the matching sensitivity * @throws IllegalArgumentException if the name and currency do not match an entry */ public CurrencyParameterSensitivity getSensitivity(MarketDataName name, Currency currency) { return findSensitivity(name, currency) .orElseThrow(() -> new IllegalArgumentException(Messages.format( "Unable to find sensitivity: {} for {}", name, currency))); } /** * Finds a single sensitivity instance by name and currency. *

* If the sensitivity is not found, optional empty is returned. * * @param name the curve name to find * @param currency the currency to find * @return the matching sensitivity */ public Optional findSensitivity(MarketDataName name, Currency currency) { return sensitivities.stream() .filter(sens -> sens.getMarketDataName().equals(name) && sens.getCurrency().equals(currency)) .findFirst(); } //------------------------------------------------------------------------- /** * Combines this parameter sensitivities with another instance. *

* This returns a new sensitivity instance with the specified sensitivity added. * This instance is immutable and unaffected by this method. *

* The sensitivities are merged using market data name and currency as a key. * The parameter metadata is not checked, thus the caller must ensure the sensitivities * are compatible with the same metadata and parameter count. * To combine taking the metadata into account, use {@link #mergedWith(CurrencyParameterSensitivities)}. * * @param other the other parameter sensitivity * @return an instance based on this one, with the other instance added */ public CurrencyParameterSensitivities combinedWith(CurrencyParameterSensitivity other) { List mutable = new ArrayList<>(sensitivities); insert(mutable, other); return new CurrencyParameterSensitivities(ImmutableList.copyOf(mutable)); } /** * Combines this parameter sensitivities with another instance. *

* This returns a new sensitivity instance with a combined list of parameter sensitivities. * This instance is immutable and unaffected by this method. *

* The sensitivities are merged using market data name and currency as a key. * The parameter metadata is not checked, thus the caller must ensure the sensitivities * are compatible with the same metadata and parameter count. * To combine taking the metadata into account, use {@link #mergedWith(CurrencyParameterSensitivities)}. * * @param other the other parameter sensitivities * @return an instance based on this one, with the other instance added */ public CurrencyParameterSensitivities combinedWith(CurrencyParameterSensitivities other) { List mutable = new ArrayList<>(sensitivities); for (CurrencyParameterSensitivity otherSens : other.sensitivities) { insert(mutable, otherSens); } return new CurrencyParameterSensitivities(ImmutableList.copyOf(mutable)); } // inserts a sensitivity into the mutable list in the right location // merges the entry with an existing entry if the key matches private static void insert(List mutable, CurrencyParameterSensitivity addition) { int index = Collections.binarySearch( mutable, addition, CurrencyParameterSensitivity::compareKey); if (index >= 0) { CurrencyParameterSensitivity base = mutable.get(index); DoubleArray combined = base.getSensitivity().plus(addition.getSensitivity()); mutable.set(index, base.withSensitivity(combined)); } else { int insertionPoint = -(index + 1); mutable.add(insertionPoint, addition); } } /** * Merges this parameter sensitivities with another instance taking the metadata into account. *

* This returns a new sensitivity instance with a combined set of parameter sensitivities. * This instance is immutable and unaffected by this method. *

* The sensitivities are merged using market data name and currency as a key. * Each sensitivity is then merged taking into account the metadata, such as the tenor. * As such, this method can only be used if the parameter metadata instances are not be empty. * * @param other the other parameter sensitivities * @return an instance based on this one, with the other instance added * @throws IllegalArgumentException if any metadata instance is empty */ public CurrencyParameterSensitivities mergedWith(CurrencyParameterSensitivities other) { return toBuilder().add(other).build(); } //------------------------------------------------------------------------- /** * Checks and adjusts the market data names. *

* The supplied function is invoked for each market data name in this sensitivities. * If the function returns the same name for two different inputs, the sensitivity values will be summed. * A typical use case would be to convert index names to curve names valid for an underlying system. * * @param nameFn the function for checking and adjusting the name * @return the adjusted sensitivity * @throws RuntimeException if the function throws an exception */ public CurrencyParameterSensitivities withMarketDataNames(Function, MarketDataName> nameFn) { CurrencyParameterSensitivitiesBuilder builder = CurrencyParameterSensitivities.builder(); for (CurrencyParameterSensitivity sensitivity : sensitivities) { builder.add(sensitivity.toBuilder() .marketDataName(nameFn.apply(sensitivity.getMarketDataName())) .build()); } return builder.build(); } /** * Checks and adjusts the parameter metadata. *

* The supplied function is invoked for each parameter metadata in this sensitivities. * If the function returns the same metadata for two different inputs, the sensitivity values will be summed. * A typical use case would be to normalize parameter metadata tenors to be valid for an underlying system. * * @param mdFn the function for checking and adjusting the metadata * @return the adjusted sensitivity * @throws IllegalArgumentException if any metadata instance is empty * @throws RuntimeException if the function throws an exception */ public CurrencyParameterSensitivities withParameterMetadatas(UnaryOperator mdFn) { return toBuilder().mapMetadata(mdFn).build(); } //------------------------------------------------------------------------- /** * Converts the sensitivities in this instance to an equivalent in the specified currency. *

* Any FX conversion that is required will use rates from the provider. * * @param resultCurrency the currency of the result * @param rateProvider the provider of FX rates * @return the sensitivity object expressed in terms of the result currency * @throws RuntimeException if no FX rate could be found */ @Override public CurrencyParameterSensitivities convertedTo(Currency resultCurrency, FxRateProvider rateProvider) { List mutable = new ArrayList<>(); for (CurrencyParameterSensitivity sens : sensitivities) { insert(mutable, sens.convertedTo(resultCurrency, rateProvider)); } return new CurrencyParameterSensitivities(ImmutableList.copyOf(mutable)); } //------------------------------------------------------------------------- /** * Splits this sensitivity instance. *

* This examines each individual sensitivity to see if it can be {@link CurrencyParameterSensitivity#split() split}. * If any can be split, the result will contain the combination of the split sensitivities. * * @return this sensitivity, with any combined sensitivities split */ public CurrencyParameterSensitivities split() { if (!sensitivities.stream().anyMatch(s -> s.getParameterSplit().isPresent())) { return this; } return of(sensitivities.stream() .flatMap(s -> s.split().stream()) .collect(toImmutableList())); } //------------------------------------------------------------------------- /** * Returns the total of the sensitivity values. *

* The result is the total of all values, as converted to the specified currency. * Any FX conversion that is required will use rates from the provider. * * @param resultCurrency the currency of the result * @param rateProvider the provider of FX rates * @return the total sensitivity * @throws RuntimeException if no FX rate could be found */ public CurrencyAmount total(Currency resultCurrency, FxRateProvider rateProvider) { CurrencyParameterSensitivities converted = convertedTo(resultCurrency, rateProvider); double total = converted.sensitivities.stream() .mapToDouble(s -> s.getSensitivity().sum()) .sum(); return CurrencyAmount.of(resultCurrency, total); } /** * Returns the total of the sensitivity values. *

* The result is the total of all values, in whatever currency they are defined. * * @return the total sensitivity */ public MultiCurrencyAmount total() { return sensitivities.stream() .map(CurrencyParameterSensitivity::total) .collect(toMultiCurrencyAmount()); } //------------------------------------------------------------------------- /** * Returns an instance with the sensitivity values multiplied by the specified factor. *

* The result will consist of the same entries, but with each sensitivity value multiplied. * This instance is immutable and unaffected by this method. * * @param factor the multiplicative factor * @return an instance based on this one, with each sensitivity multiplied by the factor */ public CurrencyParameterSensitivities multipliedBy(double factor) { return mapSensitivities(s -> s * factor); } /** * Returns an instance with the specified operation applied to the sensitivity values. *

* The result will consist of the same entries, but with the operator applied to each sensitivity value. * This instance is immutable and unaffected by this method. *

* This is used to apply a mathematical operation to the sensitivity values. * For example, the operator could multiply the sensitivities by a constant, or take the inverse. *

   *   inverse = base.mapSensitivities(value -> 1 / value);
   * 
* * @param operator the operator to be applied to the sensitivities * @return an instance based on this one, with the operator applied to the sensitivity values */ public CurrencyParameterSensitivities mapSensitivities(DoubleUnaryOperator operator) { return sensitivities.stream() .map(s -> s.mapSensitivity(operator)) .collect( Collectors.collectingAndThen( Guavate.toImmutableList(), CurrencyParameterSensitivities::new)); } /** * Returns an instance with an operation applied to each indexed value in the sensitivity values. *

* Each value in the sensitivity array will be operated on. * The function receives both the index and the value. * For example, the operator could multiply the sensitivities by the index. *

   *   result = base.mapSensitivityWithIndex((index, value) -> index * value);
   * 
* This instance is immutable and unaffected by this method. * * @param function the function to be applied to the sensitivities * @return an instance based on this one, with the operator applied to the sensitivity values */ public CurrencyParameterSensitivities mapSensitivitiesWithIndex(IntDoubleToDoubleFunction function) { return sensitivities.stream() .map(s -> s.mapSensitivityWithIndex(function)) .collect( Collectors.collectingAndThen( Guavate.toImmutableList(), CurrencyParameterSensitivities::new)); } //------------------------------------------------------------------------- /** * Checks if this sensitivity equals another within the specified tolerance. *

* This returns true if the two instances have the same keys, with arrays of the * same length, where the {@code double} values are equal within the specified tolerance. * * @param other the other sensitivity * @param tolerance the tolerance * @return true if equal up to the tolerance */ public boolean equalWithTolerance(CurrencyParameterSensitivities other, double tolerance) { List mutable = new ArrayList<>(other.sensitivities); // for each sensitivity in this instance, find matching in other instance for (CurrencyParameterSensitivity sens1 : sensitivities) { // list is already sorted so binary search is safe int index = Collections.binarySearch(mutable, sens1, CurrencyParameterSensitivity::compareKey); if (index >= 0) { // matched, so must be equal CurrencyParameterSensitivity sens2 = mutable.get(index); if (!sens1.getSensitivity().equalWithTolerance(sens2.getSensitivity(), tolerance)) { return false; } mutable.remove(index); } else { // did not match, so must be zero if (!sens1.getSensitivity().equalZeroWithTolerance(tolerance)) { return false; } } } // all that remain from other instance must be zero for (CurrencyParameterSensitivity sens2 : mutable) { if (!sens2.getSensitivity().equalZeroWithTolerance(tolerance)) { return false; } } return true; } //------------------------------------------------------------------------- /** * Returns a builder populated with the set of sensitivities from this instance. *

* The builder takes into account the parameter metadata when creating the sensitivity map. * As such, the parameter metadata added to the builder must not be empty. * * @return the builder * @throws IllegalArgumentException if any metadata instance is empty */ public CurrencyParameterSensitivitiesBuilder toBuilder() { return new CurrencyParameterSensitivitiesBuilder(sensitivities); } //------------------------- AUTOGENERATED START ------------------------- /** * The meta-bean for {@code CurrencyParameterSensitivities}. * @return the meta-bean, not null */ public static CurrencyParameterSensitivities.Meta meta() { return CurrencyParameterSensitivities.Meta.INSTANCE; } static { MetaBean.register(CurrencyParameterSensitivities.Meta.INSTANCE); } /** * The serialization version id. */ private static final long serialVersionUID = 1L; @Override public CurrencyParameterSensitivities.Meta metaBean() { return CurrencyParameterSensitivities.Meta.INSTANCE; } //----------------------------------------------------------------------- /** * Gets the parameter sensitivities. *

* Each entry includes details of the {@link ParameterizedData} it relates to. * @return the value of the property, not null */ public ImmutableList getSensitivities() { return sensitivities; } //----------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { CurrencyParameterSensitivities other = (CurrencyParameterSensitivities) obj; return JodaBeanUtils.equal(sensitivities, other.sensitivities); } return false; } @Override public int hashCode() { int hash = getClass().hashCode(); hash = hash * 31 + JodaBeanUtils.hashCode(sensitivities); return hash; } @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("CurrencyParameterSensitivities{"); buf.append("sensitivities").append('=').append(JodaBeanUtils.toString(sensitivities)); buf.append('}'); return buf.toString(); } //----------------------------------------------------------------------- /** * The meta-bean for {@code CurrencyParameterSensitivities}. */ 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 sensitivities} property. */ @SuppressWarnings({"unchecked", "rawtypes" }) private final MetaProperty> sensitivities = DirectMetaProperty.ofImmutable( this, "sensitivities", CurrencyParameterSensitivities.class, (Class) ImmutableList.class); /** * The meta-properties. */ private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( this, null, "sensitivities"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case 1226228605: // sensitivities return sensitivities; } return super.metaPropertyGet(propertyName); } @Override public BeanBuilder builder() { return new CurrencyParameterSensitivities.Builder(); } @Override public Class beanType() { return CurrencyParameterSensitivities.class; } @Override public Map> metaPropertyMap() { return metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code sensitivities} property. * @return the meta-property, not null */ public MetaProperty> sensitivities() { return sensitivities; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case 1226228605: // sensitivities return ((CurrencyParameterSensitivities) bean).getSensitivities(); } 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 CurrencyParameterSensitivities}. */ private static final class Builder extends DirectPrivateBeanBuilder { private List sensitivities = ImmutableList.of(); /** * Restricted constructor. */ private Builder() { } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case 1226228605: // sensitivities return sensitivities; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @SuppressWarnings("unchecked") @Override public Builder set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case 1226228605: // sensitivities this.sensitivities = (List) newValue; break; default: throw new NoSuchElementException("Unknown property: " + propertyName); } return this; } @Override public CurrencyParameterSensitivities build() { return new CurrencyParameterSensitivities( sensitivities); } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("CurrencyParameterSensitivities.Builder{"); buf.append("sensitivities").append('=').append(JodaBeanUtils.toString(sensitivities)); buf.append('}'); return buf.toString(); } } //-------------------------- AUTOGENERATED END -------------------------- }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy