com.opengamma.strata.function.calculation.swap.SwapMeasureCalculations.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.function.calculation.swap.SwapMeasureCalculations.java

Source

/**
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.function.calculation.swap;

import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.basics.market.MarketData;
import com.opengamma.strata.basics.market.MarketDataKey;
import com.opengamma.strata.calc.marketdata.CalculationMarketData;
import com.opengamma.strata.calc.runner.function.result.MultiCurrencyValuesArray;
import com.opengamma.strata.calc.runner.function.result.ScenarioResult;
import com.opengamma.strata.calc.runner.function.result.SingleScenarioResult;
import com.opengamma.strata.calc.runner.function.result.ValuesArray;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.function.calculation.rate.MarketDataUtils;
import com.opengamma.strata.market.amount.CashFlows;
import com.opengamma.strata.market.amount.LegAmount;
import com.opengamma.strata.market.amount.LegAmounts;
import com.opengamma.strata.market.amount.SwapLegAmount;
import com.opengamma.strata.market.curve.CurveCurrencyParameterSensitivities;
import com.opengamma.strata.market.curve.CurveCurrencyParameterSensitivity;
import com.opengamma.strata.market.curve.NodalCurve;
import com.opengamma.strata.market.explain.ExplainMap;
import com.opengamma.strata.market.key.DiscountCurveKey;
import com.opengamma.strata.market.key.MarketDataKeys;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.pricer.rate.MarketDataRatesProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.pricer.sensitivity.CurveGammaCalculator;
import com.opengamma.strata.pricer.swap.DiscountingSwapLegPricer;
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer;
import com.opengamma.strata.product.swap.ExpandedSwap;
import com.opengamma.strata.product.swap.ExpandedSwapLeg;
import com.opengamma.strata.product.swap.NotionalPaymentPeriod;
import com.opengamma.strata.product.swap.PaymentPeriod;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.SwapTrade;

/**
 * Multi-scenario measure calculations for Swap trades.
 * <p>
 * Each method corresponds to a measure, typically calculated by one or more calls to the pricer.
 */
final class SwapMeasureCalculations {

    /**
     * The pricer to use.
     */
    private static final DiscountingSwapProductPricer PRICER = DiscountingSwapProductPricer.DEFAULT;
    /**
     * One basis point, expressed as a {@code double}.
     */
    private static final double ONE_BASIS_POINT = 1e-4;
    /**
     * Special marker value used in place of null.
     */
    private static CurrencyAmount NOT_FOUND = CurrencyAmount.zero(Currency.XXX);

    // restricted constructor
    private SwapMeasureCalculations() {
    }

    //-------------------------------------------------------------------------
    // calculates par rate for all scenarios
    static ValuesArray parRate(SwapTrade trade, ExpandedSwap product, CalculationMarketData marketData) {

        return ValuesArray.of(marketData.getScenarioCount(),
                i -> calculateParRate(product, marketData.scenario(i)));
    }

    // par rate for one scenario
    private static double calculateParRate(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.parRate(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates par spread for all scenarios
    static ValuesArray parSpread(SwapTrade trade, ExpandedSwap product, CalculationMarketData marketData) {

        return ValuesArray.of(marketData.getScenarioCount(),
                i -> calculateParSpread(product, marketData.scenario(i)));
    }

    // par spread for one scenario
    private static double calculateParSpread(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.parSpread(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates present value for all scenarios
    static MultiCurrencyValuesArray presentValue(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return MultiCurrencyValuesArray.of(marketData.getScenarioCount(),
                i -> calculatePresentValue(product, marketData.scenario(i)));
    }

    // present value for one scenario
    private static MultiCurrencyAmount calculatePresentValue(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.presentValue(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates explain present value for all scenarios
    static ScenarioResult<ExplainMap> explainPresentValue(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return ScenarioResult.of(marketData.getScenarioCount(),
                i -> calculateExplainPresentValue(product, marketData.scenario(i)));
    }

    // explain present value for one scenario
    private static ExplainMap calculateExplainPresentValue(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.explainPresentValue(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates cash flows for all scenarios
    static ScenarioResult<CashFlows> cashFlows(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return ScenarioResult.of(marketData.getScenarioCount(),
                i -> calculateCashFlows(product, marketData.scenario(i)));
    }

    // cash flows for one scenario
    private static CashFlows calculateCashFlows(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.cashFlows(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates PV01 for all scenarios
    static MultiCurrencyValuesArray pv01(SwapTrade trade, ExpandedSwap product, CalculationMarketData marketData) {

        return MultiCurrencyValuesArray.of(marketData.getScenarioCount(),
                i -> calculatePv01(product, marketData.scenario(i)));
    }

    // PV01 for one scenario
    private static MultiCurrencyAmount calculatePv01(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        PointSensitivities pointSensitivity = PRICER.presentValueSensitivity(product, provider).build();
        return provider.curveParameterSensitivity(pointSensitivity).total().multipliedBy(ONE_BASIS_POINT);
    }

    //-------------------------------------------------------------------------
    // calculates bucketed PV01 for all scenarios
    static ScenarioResult<CurveCurrencyParameterSensitivities> bucketedPv01(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return ScenarioResult.of(marketData.getScenarioCount(),
                i -> calculateBucketedPv01(product, marketData.scenario(i)));
    }

    // bucketed PV01 for one scenario
    private static CurveCurrencyParameterSensitivities calculateBucketedPv01(ExpandedSwap product,
            MarketData marketData) {

        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        PointSensitivities pointSensitivity = PRICER.presentValueSensitivity(product, provider).build();
        return provider.curveParameterSensitivity(pointSensitivity).multipliedBy(ONE_BASIS_POINT);
    }

    //-------------------------------------------------------------------------
    // calculates bucketed gamma PV01 for all scenarios
    static ScenarioResult<CurveCurrencyParameterSensitivities> bucketedGammaPv01(SwapTrade trade,
            ExpandedSwap product, CalculationMarketData marketData) {

        return ScenarioResult.of(marketData.getScenarioCount(),
                i -> calculateBucketedGammaPv01(trade, product, marketData.scenario(i)));
    }

    // bucketed gamma PV01 for one scenario
    private static CurveCurrencyParameterSensitivities calculateBucketedGammaPv01(SwapTrade trade,
            ExpandedSwap product, MarketData marketData) {

        // find the curve and check it is valid
        if (product.isCrossCurrency()) {
            throw new IllegalArgumentException(
                    "Implementation only supports a single curve, but swap is cross-currency");
        }
        Currency currency = product.getLegs().get(0).getCurrency();
        NodalCurve nodalCurve = marketData.getValue(DiscountCurveKey.of(currency)).toNodalCurve();

        // find indices and validate there is only one curve
        Set<Index> indices = trade.getProduct().allIndices();
        validateSingleCurve(indices, marketData, nodalCurve);

        // calculate gamma
        CurveCurrencyParameterSensitivity gamma = CurveGammaCalculator.DEFAULT.calculateSemiParallelGamma(
                nodalCurve, currency, c -> calculateCurveSensitivity(product, currency, indices, marketData, c));
        return CurveCurrencyParameterSensitivities.of(gamma).multipliedBy(ONE_BASIS_POINT * ONE_BASIS_POINT);
    }

    // validates that the indices all resolve to the single specified curve
    private static void validateSingleCurve(Set<Index> indices, MarketData marketData, NodalCurve nodalCurve) {
        Set<MarketDataKey<?>> differentForwardCurves = indices.stream().map(MarketDataKeys::indexCurve)
                .filter(k -> !nodalCurve.equals(marketData.getValue(k))).collect(toSet());
        if (!differentForwardCurves.isEmpty()) {
            throw new IllegalArgumentException(Messages
                    .format("Implementation only supports a single curve, but discounting curve is different from "
                            + "index curves for indices: {}", differentForwardCurves));
        }
    }

    // calculates the sensitivity
    private static CurveCurrencyParameterSensitivity calculateCurveSensitivity(ExpandedSwap expandedSwap,
            Currency currency, Set<? extends Index> indices, MarketData marketData, NodalCurve bumpedCurve) {

        RatesProvider ratesProvider = MarketDataUtils.toSingleCurveRatesProvider(marketData, currency, indices,
                bumpedCurve);
        PointSensitivities pointSensitivities = DiscountingSwapProductPricer.DEFAULT
                .presentValueSensitivity(expandedSwap, ratesProvider).build();
        CurveCurrencyParameterSensitivities paramSensitivities = ratesProvider
                .curveParameterSensitivity(pointSensitivities);
        return Iterables.getOnlyElement(paramSensitivities.getSensitivities());
    }

    //-------------------------------------------------------------------------
    // calculates accrued interest for all scenarios
    static MultiCurrencyValuesArray accruedInterest(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return MultiCurrencyValuesArray.of(marketData.getScenarioCount(),
                i -> calculateAccruedInterest(product, marketData.scenario(i)));
    }

    // current cash for one scenario
    private static MultiCurrencyAmount calculateAccruedInterest(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.accruedInterest(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates leg initial notional for all scenarios
    static SingleScenarioResult<LegAmounts> legInitialNotional(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        LegAmounts legInitialNotional = calculateLegInitialNotional(trade);
        return SingleScenarioResult.of(marketData.getScenarioCount(), legInitialNotional);
    }

    // leg initial notional, which is the same for all scenarios
    // package-scoped for testing
    static LegAmounts calculateLegInitialNotional(SwapTrade trade) {
        List<Pair<SwapLeg, CurrencyAmount>> notionals = trade.getProduct().getLegs().stream()
                .map(leg -> Pair.of(leg, buildLegNotional(leg))).collect(toList());
        CurrencyAmount firstNotional = notionals.stream().filter(pair -> pair.getSecond() != NOT_FOUND)
                .map(pair -> pair.getSecond()).findFirst()
                .orElseThrow(() -> new IllegalArgumentException("No notional found on any swap leg"));
        notionals = notionals.stream()
                .map(pair -> pair.getSecond() != NOT_FOUND ? pair : Pair.of(pair.getFirst(), firstNotional))
                .collect(toList());
        ImmutableList<LegAmount> legAmounts = notionals.stream()
                .map(pair -> SwapLegAmount.of(pair.getFirst(), pair.getSecond())).collect(toImmutableList());
        return LegAmounts.of(legAmounts);
    }

    // find the notional
    private static CurrencyAmount buildLegNotional(SwapLeg leg) {
        // try RateCalculationSwapLeg first to avoid expand
        if (leg instanceof RateCalculationSwapLeg) {
            RateCalculationSwapLeg rcleg = (RateCalculationSwapLeg) leg;
            return CurrencyAmount.of(leg.getCurrency(),
                    Math.abs(rcleg.getNotionalSchedule().getAmount().getInitialValue()));
        }
        // expand and check for NotionalPaymentPeriod
        ExpandedSwapLeg expanded = leg.expand();
        PaymentPeriod firstPaymentPeriod = expanded.getPaymentPeriods().get(0);
        if (firstPaymentPeriod instanceof NotionalPaymentPeriod) {
            NotionalPaymentPeriod pp = (NotionalPaymentPeriod) firstPaymentPeriod;
            return pp.getNotionalAmount().positive();
        } else {
            return NOT_FOUND;
        }
    }

    //-------------------------------------------------------------------------
    // calculates leg present value for all scenarios
    static ScenarioResult<LegAmounts> legPresentValue(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return ScenarioResult.of(marketData.getScenarioCount(),
                i -> calculateLegPresentValue(product, marketData.scenario(i)));
    }

    // leg present value for one scenario
    private static LegAmounts calculateLegPresentValue(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        List<LegAmount> legAmounts = product.getLegs().stream().map(leg -> legAmount(leg, provider))
                .collect(Collectors.toList());
        return LegAmounts.of(legAmounts);
    }

    // present value for a leg
    private static SwapLegAmount legAmount(ExpandedSwapLeg leg, RatesProvider provider) {
        CurrencyAmount amount = DiscountingSwapLegPricer.DEFAULT.presentValue(leg, provider);
        return SwapLegAmount.of(leg, amount);
    }

    //-------------------------------------------------------------------------
    // calculates currency exposure for all scenarios
    static MultiCurrencyValuesArray currencyExposure(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return MultiCurrencyValuesArray.of(marketData.getScenarioCount(),
                i -> calculateCurrencyExposure(product, marketData.scenario(i)));
    }

    // currency exposure for one scenario
    private static MultiCurrencyAmount calculateCurrencyExposure(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.currencyExposure(product, provider);
    }

    //-------------------------------------------------------------------------
    // calculates current cash for all scenarios
    static MultiCurrencyValuesArray currentCash(SwapTrade trade, ExpandedSwap product,
            CalculationMarketData marketData) {

        return MultiCurrencyValuesArray.of(marketData.getScenarioCount(),
                i -> calculateCurrentCash(product, marketData.scenario(i)));
    }

    // current cash for one scenario
    private static MultiCurrencyAmount calculateCurrentCash(ExpandedSwap product, MarketData marketData) {
        RatesProvider provider = MarketDataRatesProvider.of(marketData);
        return PRICER.currentCash(product, provider);
    }

}