diff --git a/examples/pom.xml b/examples/pom.xml index 0f04172f0a..00aeddc62a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.22-xplain-2 + 2.12.22-xplain-3 jar Strata-Examples Example code to demonstrate use of Strata diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index fec3e9fa5d..76e0e56a70 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-basics @@ -21,6 +21,12 @@ strata-collect + + + org.slf4j + slf4j-api + + org.junit.jupiter diff --git a/modules/basics/src/main/java/com/opengamma/strata/basics/ReferenceDataHolder.java b/modules/basics/src/main/java/com/opengamma/strata/basics/ReferenceDataHolder.java new file mode 100644 index 0000000000..973b1adf00 --- /dev/null +++ b/modules/basics/src/main/java/com/opengamma/strata/basics/ReferenceDataHolder.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.basics; + +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.opengamma.strata.collect.ArgChecker; + +/** + * Associates {@link ReferenceData} with the current execution thread. + *

+ * This class provides a series of static methods that delegate to {@link InheritableThreadLocalReferenceDataHolderStrategy}. + * This strategy uses an {@link InheritableThreadLocal} to store {@code ReferenceData} against a thread. + * + *

+ */ +public class ReferenceDataHolder { + private ReferenceDataHolder() { + // prevent instantiation + } + + private static final InheritableThreadLocalReferenceDataHolderStrategy STRATEGY = new InheritableThreadLocalReferenceDataHolderStrategy(); + + /** + * Sets the current reference data for this thread. + * + * @param refData the new value to hold (should never be null). + * @throws IllegalArgumentException if {@code refData} is null. + */ + public static void setReferenceData(ReferenceData refData) { + ArgChecker.notNull(refData, "refData"); + STRATEGY.setReferenceData(refData); + } + + /** + * Gets the current reference data for this thread. + * If it has not previously been set for this thread then it will be null. + * + * @return current reference data + */ + public static ReferenceData getReferenceData() { + return STRATEGY.getReferenceData(); + } + + /** + * Gets the current reference data for this thread and, if it is not set, return a fallback value instead. + * + * @param other the fallback value to return if the reference data has not been set for this thread (should never be null). + * @return the reference data for this thread, or the provided fallback value + * @throws IllegalArgumentException if {@code other} is null. + */ + public static ReferenceData getReferenceDataWithFallback(ReferenceData other) { + ArgChecker.notNull(other, "other"); + return STRATEGY.getReferenceDataWithFallback(other); + } + + /** + * Executes a function to return a value with reference data set for the duration of the function and then + * cleared afterwards. + * + * @param referenceData the new value to hold (should never be null). + * @param fn the function to call to return a value + * @return the value returned from the function + * @param type of value + */ + public static T withReferenceData(ReferenceData referenceData, Supplier fn) { + setReferenceData(referenceData); + try { + T result = fn.get(); + return result; + } finally { + clearReferenceData(); + } + } + + /** + * Executes a function that returns no value with reference data set for the duration of the function and then + * cleared afterwards. + * + * @param referenceData the new value to hold (should never be null). + * @param fn the function to call + */ + public static void withReferenceData(ReferenceData referenceData, Runnable fn) { + setReferenceData(referenceData); + try { + fn.run(); + } finally { + clearReferenceData(); + } + } + + /** + * Clears the current reference data for this thread. + */ + public static void clearReferenceData() { + STRATEGY.clearReferenceData(); + } + + private static class InheritableThreadLocalReferenceDataHolderStrategy { + private Logger log = LoggerFactory.getLogger(ReferenceDataHolder.class); + private static final ThreadLocal HOLDER = new InheritableThreadLocal<>(); + + public void clearReferenceData() { + HOLDER.remove(); + } + + public ReferenceData getReferenceData() { + return HOLDER.get(); + } + + public void setReferenceData(ReferenceData refData) { + HOLDER.set(refData); + } + + public ReferenceData getReferenceDataWithFallback(ReferenceData other) { + final ReferenceData result = getReferenceData(); + if (result == null) { + log.warn("No reference data saved in thread, falling back to default."); + return other; + } + return result; + } + } +} diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 30fb1a764b..3173c50eb2 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-calc diff --git a/modules/calc/src/main/java/com/opengamma/strata/calc/runner/CalculationTask.java b/modules/calc/src/main/java/com/opengamma/strata/calc/runner/CalculationTask.java index 3a5fb22974..5c61052a7e 100644 --- a/modules/calc/src/main/java/com/opengamma/strata/calc/runner/CalculationTask.java +++ b/modules/calc/src/main/java/com/opengamma/strata/calc/runner/CalculationTask.java @@ -29,6 +29,7 @@ import com.google.common.collect.Sets; import com.opengamma.strata.basics.CalculationTarget; import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.ReferenceDataHolder; import com.opengamma.strata.basics.ReferenceDataNotFoundException; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyPair; @@ -231,7 +232,9 @@ private Map> calculate(ScenarioMarketData marketData, Referen Set measures = Sets.intersection(requestedMeasures, supportedMeasures); Map> map = ImmutableMap.of(); if (!measures.isEmpty()) { - map = function.calculate(target, measures, parameters, marketData, refData); + // TODO: everything should ideally be resolved in the CalculationFunction, not relying on ReferenceDataHolder lower down. + map = ReferenceDataHolder.withReferenceData(refData, + () -> function.calculate(target, measures, parameters, marketData, refData)); } // check if result does not contain all requested measures if (!map.keySet().containsAll(requestedMeasures)) { diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 7d3cc33231..053d791e78 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 040e91b11f..080595495b 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 3506e58cd3..99b1536188 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index e1c28baf8d..bcc4fcaca3 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index bc3e063ac0..6cce21d600 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 2c6223a5e4..4823de9778 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index f219de9418..6ebc2541b2 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 pom Strata-Parent OpenGamma Strata Parent diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index bcf37d73f4..a8a8dd9e65 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-pricer diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/fx/DiscountFxForwardRates.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/fx/DiscountFxForwardRates.java index 85178418bd..8341350e9a 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/fx/DiscountFxForwardRates.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/fx/DiscountFxForwardRates.java @@ -26,6 +26,7 @@ import org.joda.beans.impl.direct.DirectPrivateBeanBuilder; import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.ReferenceDataHolder; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.currency.CurrencyPair; @@ -152,7 +153,8 @@ public LocalDate getValuationDate() { private LocalDate spotDate() { try { FxSwapConvention fxSwapConvention = FxSwapConvention.of(currencyPair); - return fxSwapConvention.calculateSpotDateFromTradeDate(valuationDate, ReferenceData.standard()); + ReferenceData refData = ReferenceDataHolder.getReferenceDataWithFallback(ReferenceData.standard()); + return fxSwapConvention.calculateSpotDateFromTradeDate(valuationDate, refData); } catch (IllegalArgumentException e) { return valuationDate; } diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingFxResetNotionalExchangePricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingFxResetNotionalExchangePricer.java index b67f8b6f32..b2e9673b69 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingFxResetNotionalExchangePricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingFxResetNotionalExchangePricer.java @@ -8,9 +8,9 @@ import java.time.LocalDate; import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.ReferenceDataHolder; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; -import com.opengamma.strata.basics.currency.CurrencyPair; import com.opengamma.strata.basics.currency.MultiCurrencyAmount; import com.opengamma.strata.market.explain.ExplainKey; import com.opengamma.strata.market.explain.ExplainMapBuilder; @@ -19,7 +19,6 @@ import com.opengamma.strata.pricer.fx.FxIndexRates; import com.opengamma.strata.pricer.rate.RatesProvider; import com.opengamma.strata.pricer.swap.SwapPaymentEventPricer; -import com.opengamma.strata.product.fx.type.FxSwapConvention; import com.opengamma.strata.product.swap.FxResetNotionalExchange; /** @@ -109,6 +108,17 @@ public void explainPresentValue(FxResetNotionalExchange event, RatesProvider pro } //------------------------------------------------------------------------- + /** + * Calculates the currency exposure of a single payment event. + *

+ * If {@link ReferenceDataHolder} has been populated for this thread, it will be used to obtain the calendar + * for calculating the spot date from the valuation date. Otherwise the {@link ReferenceData#standard() standard} + * calendar will be used instead. + * + * @param event the event + * @param provider the rates provider + * @return the currency exposure + */ @Override public MultiCurrencyAmount currencyExposure(FxResetNotionalExchange event, RatesProvider provider) { double dfCounterMaturity = provider.discountFactor(event.getCurrency(), event.getPaymentDate()); @@ -120,21 +130,17 @@ public MultiCurrencyAmount currencyExposure(FxResetNotionalExchange event, Rates return MultiCurrencyAmount.of(CurrencyAmount.of(event.getCurrency(), event.getNotional() * dfCounterMaturity * fxRate)); } + ReferenceData refData = ReferenceDataHolder.getReferenceDataWithFallback(ReferenceData.standard()); Currency baseCurrency = event.getReferenceCurrency(); Currency counterCurrency = event.getCurrency(); - LocalDate valuationDate = provider.getValuationDate(); - LocalDate spotDate = FxSwapConvention.of(CurrencyPair.of(baseCurrency, counterCurrency)).calculateSpotDateFromTradeDate(valuationDate, - ReferenceData.standard()); - - double dfCounterSpot = provider.discountFactor(counterCurrency, spotDate); - double dfReferenceSpot = provider.discountFactor(baseCurrency, spotDate); + double dfScaled = FxDfScaler.scaledDf(provider, refData, baseCurrency, counterCurrency, dfCounterMaturity); LocalDate maturityDate = event.getObservation().getMaturityDate(); double fxRateSpotSensitivity = rates.getFxForwardRates().rateFxSpotSensitivity(event.getReferenceCurrency(), maturityDate); return MultiCurrencyAmount.of( CurrencyAmount.of(event.getReferenceCurrency(), event.getNotional() * - fxRateSpotSensitivity * (dfCounterMaturity / dfCounterSpot) * dfReferenceSpot)); + fxRateSpotSensitivity * dfScaled)); } @Override diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingRatePaymentPeriodPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingRatePaymentPeriodPricer.java index db24bdb712..ad378091c5 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingRatePaymentPeriodPricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/DiscountingRatePaymentPeriodPricer.java @@ -11,9 +11,9 @@ import com.google.common.collect.ImmutableList; import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.ReferenceDataHolder; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; -import com.opengamma.strata.basics.currency.CurrencyPair; import com.opengamma.strata.basics.currency.MultiCurrencyAmount; import com.opengamma.strata.basics.date.DayCount; import com.opengamma.strata.collect.ArgChecker; @@ -25,7 +25,6 @@ import com.opengamma.strata.pricer.rate.RateComputationFn; import com.opengamma.strata.pricer.rate.RatesProvider; import com.opengamma.strata.pricer.swap.SwapPaymentPeriodPricer; -import com.opengamma.strata.product.fx.type.FxSwapConvention; import com.opengamma.strata.product.rate.RateComputation; import com.opengamma.strata.product.swap.CompoundingMethod; import com.opengamma.strata.product.swap.FxReset; @@ -461,6 +460,18 @@ private void explainPresentValue( } //------------------------------------------------------------------------- + + /** + * Calculates the currency exposure of a single payment period. + *

+ * If {@link ReferenceDataHolder} has been populated for this thread, it will be used to obtain the calendar + * for calculating the spot date from the valuation date. Otherwise the {@link ReferenceData#standard() standard} + * calendar will be used instead. + * + * @param period the period + * @param provider the rates provider + * @return the currency exposure + */ @Override public MultiCurrencyAmount currencyExposure(RatePaymentPeriod period, RatesProvider provider) { double dfCounterMaturity = provider.discountFactor(period.getCurrency(), period.getPaymentDate()); @@ -474,20 +485,16 @@ public MultiCurrencyAmount currencyExposure(RatePaymentPeriod period, RatesProvi return MultiCurrencyAmount.of(period.getCurrency(), accrualWithNotional(period, period.getNotional() * fxRate * dfCounterMaturity, provider)); } + ReferenceData refData = ReferenceDataHolder.getReferenceDataWithFallback(ReferenceData.standard()); Currency baseCurrency = fxReset.getReferenceCurrency(); Currency counterCurrency = period.getCurrency(); - LocalDate valuationDate = provider.getValuationDate(); - LocalDate spotDate = FxSwapConvention.of(CurrencyPair.of(baseCurrency, counterCurrency)).calculateSpotDateFromTradeDate(valuationDate, - ReferenceData.standard()); - - double dfCounterSpot = provider.discountFactor(counterCurrency, spotDate); - double dfReferenceSpot = provider.discountFactor(baseCurrency, spotDate); + double dfScaled = FxDfScaler.scaledDf(provider, refData, baseCurrency, counterCurrency, dfCounterMaturity); double fxRateSpotSensitivity = rates.getFxForwardRates() .rateFxSpotSensitivity(fxReset.getReferenceCurrency(), fxReset.getObservation().getMaturityDate()); return MultiCurrencyAmount.of(fxReset.getReferenceCurrency(), accrualWithNotional(period, period.getNotional() * fxRateSpotSensitivity * - (dfCounterMaturity / dfCounterSpot) * dfReferenceSpot, provider)); + dfScaled, provider)); } return MultiCurrencyAmount.of(period.getCurrency(), accrualWithNotional(period, period.getNotional() * dfCounterMaturity, provider)); diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/FxDfScaler.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/FxDfScaler.java new file mode 100644 index 0000000000..d4173c340b --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/impl/swap/FxDfScaler.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.impl.swap; + +import java.time.LocalDate; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.currency.CurrencyPair; +import com.opengamma.strata.pricer.rate.RatesProvider; +import com.opengamma.strata.product.fx.type.FxSwapConvention; + +/** + * Utility method to scale a discount factor, used by both {@link DiscountingFxResetNotionalExchangePricer} and + * {@link DiscountingRatePaymentPeriodPricer}. + */ +public final class FxDfScaler { + + private FxDfScaler() { + // prevent instantiation + } + + /** + * Scale a discount factor by the ratio between base and counter currencies at the spot date. + * + * @param provider the rates provider + * @param refData the reference data, used to resolve the spot date from the valuation date + * @param baseCurrency the base currency + * @param counterCurrency the counter currency + * @param df the unscaled discount factor + * @return the discount factor scaled by the ratio between base and counter currency discount factors at the spot date + */ + static double scaledDf(RatesProvider provider, ReferenceData refData, Currency baseCurrency, Currency counterCurrency, double df) { + LocalDate valuationDate = provider.getValuationDate(); + LocalDate spotDate = FxSwapConvention.of(CurrencyPair.of(baseCurrency, counterCurrency)).calculateSpotDateFromTradeDate(valuationDate, + refData); + + double dfCounterSpot = provider.discountFactor(counterCurrency, spotDate); + double dfReferenceSpot = provider.discountFactor(baseCurrency, spotDate); + double dfScaled = (df / dfCounterSpot) * dfReferenceSpot; + return dfScaled; + } +} diff --git a/modules/product/pom.xml b/modules/product/pom.xml index c6438bfadf..3f7c1fe0f1 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index 001b23de69..75b3fd5cc0 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.22-xplain-2 + 2.12.22-xplain-3 .. strata-report diff --git a/pom.xml b/pom.xml index e2be915b04..ae5279538a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.22-xplain-2 + 2.12.22-xplain-3 pom Strata-Root OpenGamma Strata root