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

com.opengamma.strata.pricer.curve.RatesCurveCalibrator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.curve;

import static com.opengamma.strata.collect.Guavate.filtering;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.collect.Guavate.toImmutableMap;

import java.util.List;
import java.util.Map;
import java.util.function.Function;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.data.MarketDataFxRateProvider;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.CurveNode;
import com.opengamma.strata.market.curve.CurveParameterSize;
import com.opengamma.strata.market.curve.JacobianCalibrationMatrix;
import com.opengamma.strata.market.curve.RatesCurveGroupDefinition;
import com.opengamma.strata.market.observable.IndexQuoteId;
import com.opengamma.strata.math.impl.matrix.CommonsMatrixAlgebra;
import com.opengamma.strata.math.impl.matrix.MatrixAlgebra;
import com.opengamma.strata.math.rootfind.NewtonVectorRootFinder;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.product.ResolvedTrade;

/**
 * Curve calibrator for rates curves.
 * 

* This calibrator takes an abstract curve definition and produces real curves. *

* Curves are calibrated in groups or one or more curves. * In addition, more than one group may be calibrated together. *

* Each curve is defined using two or more {@linkplain CurveNode nodes}. * Each node primarily defines enough information to produce a reference trade. * Calibration involves pricing, and re-pricing, these trades to find the best fit * using a root finder. *

* Once calibrated, the curves are then available for use. * Each node in the curve definition becomes a parameter in the matching output curve. */ public final class RatesCurveCalibrator { /** * The standard curve calibrator. */ private static final RatesCurveCalibrator STANDARD = RatesCurveCalibrator.of(1e-9, 1e-9, 1000, CalibrationMeasures.PAR_SPREAD, CalibrationMeasures.PRESENT_VALUE); /** * The matrix algebra used for matrix inversion. */ private static final MatrixAlgebra MATRIX_ALGEBRA = new CommonsMatrixAlgebra(); /** * The root finder used for curve calibration. */ private final NewtonVectorRootFinder rootFinder; /** * The calibration measures. * This is used to compute the function for which the root is found. */ private final CalibrationMeasures measures; /** * The present value measures. * This is used to compute the present value sensitivity to market quotes stored in the metadata. */ private final CalibrationMeasures pvMeasures; //------------------------------------------------------------------------- /** * The standard curve calibrator. *

* This uses the standard tolerance of 1e-9, a maximum of 1000 steps. * The default {@link CalibrationMeasures#PAR_SPREAD} measures are used. * * @return the standard curve calibrator */ public static RatesCurveCalibrator standard() { return RatesCurveCalibrator.STANDARD; } /** * Obtains an instance specifying tolerances to use. *

* This uses a Broyden root finder. * The standard {@link CalibrationMeasures#PAR_SPREAD} and {@link CalibrationMeasures#PRESENT_VALUE} measures are used. * * @param toleranceAbs the absolute tolerance * @param toleranceRel the relative tolerance * @param stepMaximum the maximum steps * @return the curve calibrator */ public static RatesCurveCalibrator of( double toleranceAbs, double toleranceRel, int stepMaximum) { return of(toleranceAbs, toleranceRel, stepMaximum, CalibrationMeasures.PAR_SPREAD, CalibrationMeasures.PRESENT_VALUE); } /** * Obtains an instance specifying tolerances and measures to use. *

* This uses a Broyden root finder. * The standard {@link CalibrationMeasures#PRESENT_VALUE} measures are used. * * @param toleranceAbs the absolute tolerance * @param toleranceRel the relative tolerance * @param stepMaximum the maximum steps * @param measures the calibration measures, used to compute the function for which the root is found * @return the curve calibrator */ public static RatesCurveCalibrator of( double toleranceAbs, double toleranceRel, int stepMaximum, CalibrationMeasures measures) { return of(toleranceAbs, toleranceRel, stepMaximum, measures, CalibrationMeasures.PRESENT_VALUE); } /** * Obtains an instance specifying tolerances and measures to use. *

* This uses a Broyden root finder. * * @param toleranceAbs the absolute tolerance * @param toleranceRel the relative tolerance * @param stepMaximum the maximum steps * @param measures the calibration measures, used to compute the function for which the root is found * @param pvMeasures the present value measures, used to compute the present value sensitivity to market quotes * stored in the metadata * @return the curve calibrator */ public static RatesCurveCalibrator of( double toleranceAbs, double toleranceRel, int stepMaximum, CalibrationMeasures measures, CalibrationMeasures pvMeasures) { NewtonVectorRootFinder rootFinder = NewtonVectorRootFinder.broyden(toleranceAbs, toleranceRel, stepMaximum); return new RatesCurveCalibrator(rootFinder, measures, pvMeasures); } /** * Obtains an instance specifying the measures to use. * * @param rootFinder the root finder to use * @param measures the calibration measures, used to compute the function for which the root is found * @param pvMeasures the present value measures, used to compute the present value sensitivity to market quotes * stored in the metadata * @return the curve calibrator */ public static RatesCurveCalibrator of( NewtonVectorRootFinder rootFinder, CalibrationMeasures measures, CalibrationMeasures pvMeasures) { return new RatesCurveCalibrator(rootFinder, measures, pvMeasures); } //------------------------------------------------------------------------- // restricted constructor private RatesCurveCalibrator( NewtonVectorRootFinder rootFinder, CalibrationMeasures measures, CalibrationMeasures pvMeasures) { this.rootFinder = ArgChecker.notNull(rootFinder, "rootFinder"); this.measures = ArgChecker.notNull(measures, "measures"); this.pvMeasures = ArgChecker.notNull(pvMeasures, "pvMeasures"); } //------------------------------------------------------------------------- /** * Gets the measures. * * @return the measures */ public CalibrationMeasures getMeasures() { return measures; } //------------------------------------------------------------------------- /** * Calibrates a single curve group, containing one or more curves. *

* The calibration is defined using {@link RatesCurveGroupDefinition}. * Observable market data, time-series and FX are also needed to complete the calibration. * The valuation date is defined by the market data. *

* The Jacobian matrices are computed and stored in curve metadata. * * @param curveGroupDefn the curve group definition * @param marketData the market data required to build a trade for the instrument, including time-series * @param refData the reference data, used to resolve the trades * @return the rates provider resulting from the calibration */ public ImmutableRatesProvider calibrate( RatesCurveGroupDefinition curveGroupDefn, MarketData marketData, ReferenceData refData) { Map timeSeries = marketData.getTimeSeriesIds().stream() .flatMap(filtering(IndexQuoteId.class)) .collect(toImmutableMap(id -> id.getIndex(), id -> marketData.getTimeSeries(id))); ImmutableRatesProvider knownData = ImmutableRatesProvider.builder(marketData.getValuationDate()) .fxRateProvider(MarketDataFxRateProvider.of(marketData)) .timeSeries(timeSeries) .build(); return calibrate(ImmutableList.of(curveGroupDefn), knownData, marketData, refData); } /** * Calibrates a list of curve groups, each containing one or more curves. *

* The calibration is defined using a list of {@link RatesCurveGroupDefinition}. * Observable market data and existing known data are also needed to complete the calibration. *

* A curve must only exist in one group. * * @param allGroupDefns the curve group definitions * @param knownData the starting data for the calibration * @param marketData the market data required to build a trade for the instrument * @param refData the reference data, used to resolve the trades * @return the rates provider resulting from the calibration */ public ImmutableRatesProvider calibrate( List allGroupDefns, ImmutableRatesProvider knownData, MarketData marketData, ReferenceData refData) { // this method effectively takes one CurveGroupDefinition // the list is a split of the definition, not multiple independent definitions if (!knownData.getValuationDate().equals(marketData.getValuationDate())) { throw new IllegalArgumentException(Messages.format( "Valuation dates do not match: {} and {}", knownData.getValuationDate(), marketData.getValuationDate())); } // perform calibration one group at a time, building up the result by mutating these variables ImmutableRatesProvider providerCombined = knownData; ImmutableList orderPrev = ImmutableList.of(); ImmutableMap jacobians = ImmutableMap.of(); for (RatesCurveGroupDefinition groupDefn : allGroupDefns) { if (groupDefn.getEntries().isEmpty()) { continue; } RatesCurveGroupDefinition groupDefnBound = groupDefn.bindTimeSeries(knownData.getValuationDate(), knownData.getTimeSeries()); // combine all data in the group into flat lists ImmutableList trades = groupDefnBound.resolvedTrades(marketData, refData); ImmutableList initialGuesses = groupDefnBound.initialGuesses(marketData); ImmutableList orderGroup = toOrder(groupDefnBound); ImmutableList orderPrevAndGroup = ImmutableList.builder() .addAll(orderPrev) .addAll(orderGroup) .build(); // calibrate RatesProviderGenerator providerGenerator = ImmutableRatesProviderGenerator.of(providerCombined, groupDefnBound, refData); DoubleArray calibratedGroupParams = calibrateGroup(providerGenerator, trades, initialGuesses, orderGroup); ImmutableRatesProvider calibratedProvider = providerGenerator.generate(calibratedGroupParams); // use calibration to build Jacobian matrices if (groupDefnBound.isComputeJacobian()) { jacobians = updateJacobiansForGroup( calibratedProvider, trades, orderGroup, orderPrev, orderPrevAndGroup, jacobians); } ImmutableMap sensitivityToMarketQuote = ImmutableMap.of(); if (groupDefnBound.isComputePvSensitivityToMarketQuote()) { ImmutableRatesProvider providerWithJacobian = providerGenerator.generate(calibratedGroupParams, jacobians); sensitivityToMarketQuote = sensitivityToMarketQuoteForGroup(providerWithJacobian, trades, orderGroup); } orderPrev = orderPrevAndGroup; // use Jacobians to build output curves providerCombined = providerGenerator.generate(calibratedGroupParams, jacobians, sensitivityToMarketQuote); } // return the calibrated provider return providerCombined; } //------------------------------------------------------------------------- // converts a definition to the curve order list private static ImmutableList toOrder(RatesCurveGroupDefinition groupDefn) { return groupDefn.getCurveDefinitions().stream().map(def -> def.toCurveParameterSize()).collect(toImmutableList()); } //------------------------------------------------------------------------- // calibrates a single group private DoubleArray calibrateGroup( RatesProviderGenerator providerGenerator, ImmutableList trades, ImmutableList initialGuesses, ImmutableList curveOrder) { // setup for calibration Function valueCalculator = new CalibrationValue(trades, measures, providerGenerator); Function derivativeCalculator = new CalibrationDerivative(trades, measures, providerGenerator, curveOrder); // calibrate DoubleArray initialGuess = DoubleArray.copyOf(initialGuesses); return rootFinder.findRoot(valueCalculator, derivativeCalculator, initialGuess); } //------------------------------------------------------------------------- // calculates the Jacobian and builds the result, called once per group // this uses, but does not alter, data from previous groups private ImmutableMap updateJacobiansForGroup( ImmutableRatesProvider provider, ImmutableList trades, ImmutableList orderGroup, ImmutableList orderPrev, ImmutableList orderAll, ImmutableMap jacobians) { // sensitivity to all parameters in the stated order int totalParamsAll = orderAll.stream().mapToInt(e -> e.getParameterCount()).sum(); DoubleMatrix res = derivatives(trades, provider, orderAll, totalParamsAll); // jacobian direct int nbTrades = trades.size(); int totParamsGroup = orderGroup.stream().mapToInt(e -> e.getParameterCount()).sum(); int totParamsPrev = totalParamsAll - totParamsGroup; DoubleMatrix pDmCurMatrix = jacobianDirect(res, nbTrades, totParamsGroup, totParamsPrev); // jacobian indirect: when totalParamsPrevious > 0 DoubleMatrix pDmPrev = jacobianIndirect( res, pDmCurMatrix, nbTrades, totParamsGroup, totParamsPrev, orderPrev, jacobians); // add to the map of jacobians, one entry for each curve in this group ImmutableMap.Builder jacobianBuilder = ImmutableMap.builder(); jacobianBuilder.putAll(jacobians); int startIndex = 0; for (CurveParameterSize order : orderGroup) { int paramCount = order.getParameterCount(); double[][] pDmCurveArray = new double[paramCount][totalParamsAll]; // copy data for previous groups if (totParamsPrev > 0) { for (int p = 0; p < paramCount; p++) { System.arraycopy(pDmPrev.rowArray(startIndex + p), 0, pDmCurveArray[p], 0, totParamsPrev); } } // copy data for this group for (int p = 0; p < paramCount; p++) { System.arraycopy(pDmCurMatrix.rowArray(startIndex + p), 0, pDmCurveArray[p], totParamsPrev, totParamsGroup); } // build final Jacobian matrix DoubleMatrix pDmCurveMatrix = DoubleMatrix.ofUnsafe(pDmCurveArray); jacobianBuilder.put(order.getName(), JacobianCalibrationMatrix.of(orderAll, pDmCurveMatrix)); startIndex += paramCount; } return jacobianBuilder.build(); } //------------------------------------------------------------------------- private ImmutableMap sensitivityToMarketQuoteForGroup( ImmutableRatesProvider provider, ImmutableList trades, ImmutableList orderGroup) { Builder mqsGroup = new Builder<>(); int nodeIndex = 0; for (CurveParameterSize cps : orderGroup) { int nbParameters = cps.getParameterCount(); double[] mqsCurve = new double[nbParameters]; for (int looptrade = 0; looptrade < nbParameters; looptrade++) { DoubleArray mqsNode = pvMeasures.derivative(trades.get(nodeIndex), provider, orderGroup); mqsCurve[looptrade] = mqsNode.get(nodeIndex); nodeIndex++; } mqsGroup.put(cps.getName(), DoubleArray.ofUnsafe(mqsCurve)); } return mqsGroup.build(); } // calculate the derivatives private DoubleMatrix derivatives( ImmutableList trades, ImmutableRatesProvider provider, ImmutableList orderAll, int totalParamsAll) { return DoubleMatrix.ofArrayObjects( trades.size(), totalParamsAll, i -> measures.derivative(trades.get(i), provider, orderAll)); } // jacobian direct, for the current group private static DoubleMatrix jacobianDirect( DoubleMatrix res, int nbTrades, int totalParamsGroup, int totalParamsPrevious) { double[][] direct = new double[totalParamsGroup][totalParamsGroup]; for (int i = 0; i < nbTrades; i++) { System.arraycopy(res.rowArray(i), totalParamsPrevious, direct[i], 0, totalParamsGroup); } return MATRIX_ALGEBRA.getInverse(DoubleMatrix.copyOf(direct)); } // jacobian indirect, merging groups private static DoubleMatrix jacobianIndirect( DoubleMatrix res, DoubleMatrix pDmCurrentMatrix, int nbTrades, int totalParamsGroup, int totalParamsPrevious, ImmutableList orderPrevious, ImmutableMap jacobiansPrevious) { if (totalParamsPrevious == 0) { return DoubleMatrix.EMPTY; } double[][] nonDirect = new double[totalParamsGroup][totalParamsPrevious]; for (int i = 0; i < nbTrades; i++) { System.arraycopy(res.rowArray(i), 0, nonDirect[i], 0, totalParamsPrevious); } DoubleMatrix pDpPreviousMatrix = (DoubleMatrix) MATRIX_ALGEBRA.scale( MATRIX_ALGEBRA.multiply(pDmCurrentMatrix, DoubleMatrix.copyOf(nonDirect)), -1d); // all curves: order and size int[] startIndexBefore = new int[orderPrevious.size()]; for (int i = 1; i < orderPrevious.size(); i++) { startIndexBefore[i] = startIndexBefore[i - 1] + orderPrevious.get(i - 1).getParameterCount(); } // transition Matrix: all curves from previous groups double[][] transition = new double[totalParamsPrevious][totalParamsPrevious]; for (int i = 0; i < orderPrevious.size(); i++) { int paramCountOuter = orderPrevious.get(i).getParameterCount(); JacobianCalibrationMatrix thisInfo = jacobiansPrevious.get(orderPrevious.get(i).getName()); DoubleMatrix thisMatrix = thisInfo.getJacobianMatrix(); int startIndexInner = 0; for (int j = 0; j < orderPrevious.size(); j++) { int paramCountInner = orderPrevious.get(j).getParameterCount(); if (thisInfo.containsCurve(orderPrevious.get(j).getName())) { // If not, the matrix stays with 0 for (int k = 0; k < paramCountOuter; k++) { System.arraycopy( thisMatrix.rowArray(k), startIndexInner, transition[startIndexBefore[i] + k], startIndexBefore[j], paramCountInner); } } startIndexInner += paramCountInner; } } DoubleMatrix transitionMatrix = DoubleMatrix.copyOf(transition); return (DoubleMatrix) MATRIX_ALGEBRA.multiply(pDpPreviousMatrix, transitionMatrix); } //------------------------------------------------------------------------- @Override public String toString() { return Messages.format("CurveCalibrator[{}]", measures); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy