com.opengamma.strata.loader.csv.LegalEntityRatesCurvesCsvLoader Maven / Gradle / Ivy
Show all versions of strata-loader Show documentation
/*
* Copyright (C) 2017 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.loader.csv;
import static java.util.stream.Collectors.toList;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Predicate;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.CharSource;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.date.DayCount;
import com.opengamma.strata.collect.MapStream;
import com.opengamma.strata.collect.io.CsvFile;
import com.opengamma.strata.collect.io.CsvRow;
import com.opengamma.strata.collect.io.ResourceLocator;
import com.opengamma.strata.collect.result.ParseFailureException;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.loader.LoaderUtils;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.LegalEntityCurveGroup;
import com.opengamma.strata.market.curve.LegalEntityGroup;
import com.opengamma.strata.market.curve.RepoGroup;
import com.opengamma.strata.market.curve.interpolator.CurveExtrapolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
/**
* Loads a set of legal entity rates curves into memory by reading from CSV resources.
*
* There are three type of CSV files.
*
* The first file is the legal entity curve group metadata file.
* This file has the following header row:
* {@code Group Name, Curve Type, Reference, Currency, Curve Name}.
*
* - The 'Group Name' column is the name of the group of curves.
*
- The 'Curve Type' column is the type of the curve, "repo" or "issuer".
*
- The 'Reference' column is the reference group for which the curve is used, legal entity group or repo group.
*
- The 'Currency' column is the reference currency for which the curve is used.
*
- The 'Curve Name' column is the name of the curve.
*
*
* The second file is the curve settings metadata file.
* This file has the following header row:
* {@code Curve Name, Value Type, Day Count, Interpolator, Left Extrapolator, Right Extrapolator}.
*
* - The 'Curve Name' column is the name of the curve.
*
- The 'Value Type' column is the type of data in the curve, "zero" for zero rates, or "df" for discount factors.
*
- The 'Day Count' column is the name of the day count, such as "Act/365F".
*
- The 'Interpolator' column defines the interpolator to use.
*
- The 'Left Extrapolator' and 'Right Extrapolator' columns define the extrapolators to use.
*
*
* The third file is the curve values file.
* This file has the following header row:
* {@code Valuation Date, Curve Name, Date, Value, Label}.
*
* - The 'Valuation Date' column provides the valuation date, allowing data from different
* days to be stored in the same file
*
- The 'Curve Name' column is the name of the curve.
*
- The 'Date' column is the date associated with the node.
*
- The 'Value' column is value of the curve at the date.
*
- The 'Label' column is the label used to refer to the node.
*
*
* Each curve must be contained entirely within a single file, but each file may contain more than
* one curve. The curve points do not need to be ordered.
* The files must contain at least one repo curve and one issuer curve.
*/
public class LegalEntityRatesCurvesCsvLoader {
// Column headers for legal entity curve group
private static final String GROUPS_NAME = "Group Name";
private static final String GROUPS_CURVE_TYPE = "Curve Type";
private static final String GROUPS_REFERENCE = "Reference";
private static final String GROUPS_CURRENCY = "Currency";
private static final String GROUPS_CURVE_NAME = "Curve Name";
// Names used in the curve type column in the legal entity curve group
private static final String REPO = "repo";
private static final String ISSUER = "issuer";
// Column headers for curve setting
private static final String SETTINGS_CURVE_NAME = "Curve Name";
private static final String SETTINGS_VALUE_TYPE = "Value Type";
private static final String SETTINGS_DAY_COUNT = "Day Count";
private static final String SETTINGS_INTERPOLATOR = "Interpolator";
private static final String SETTINGS_LEFT_EXTRAPOLATOR = "Left Extrapolator";
private static final String SETTINGS_RIGHT_EXTRAPOLATOR = "Right Extrapolator";
// Column headers for curve nodes
private static final String CURVE_DATE = "Valuation Date";
private static final String CURVE_NAME = "Curve Name";
private static final String CURVE_POINT_DATE = "Date";
private static final String CURVE_POINT_VALUE = "Value";
private static final String CURVE_POINT_LABEL = "Label";
/**
* Names used in CSV file for value types.
*/
private static final BiMap VALUE_TYPE_MAP = ImmutableBiMap.of(
"zero", ValueType.ZERO_RATE,
"df", ValueType.DISCOUNT_FACTOR);
//-------------------------------------------------------------------------
/**
* Loads one or more CSV format curve files for a specific date.
*
* Only those quotes that match the specified date will be loaded.
*
* If the files contain a duplicate entry an exception will be thrown.
*
* @param marketDataDate the curve date to load
* @param groupsResource the curve groups CSV resource
* @param settingsResource the curve settings CSV resource
* @param curveValueResources the CSV resources for curves
* @return the loaded curves, mapped by an identifying key
* @throws IllegalArgumentException if the files contain a duplicate entry
*/
public static ImmutableList load(
LocalDate marketDataDate,
ResourceLocator groupsResource,
ResourceLocator settingsResource,
Collection curveValueResources) {
Collection curveCharSources = curveValueResources.stream().map(r -> r.getCharSource()).collect(toList());
ListMultimap map = parse(
d -> marketDataDate.equals(d),
groupsResource.getCharSource(),
settingsResource.getCharSource(),
curveCharSources);
return ImmutableList.copyOf(map.get(marketDataDate));
}
/**
* Loads one or more CSV format curve files for all available dates.
*
* If the files contain a duplicate entry an exception will be thrown.
*
* @param groupsResource the curve groups CSV resource
* @param settingsResource the curve settings CSV resource
* @param curveValueResources the CSV resources for curves
* @return the loaded curves, mapped by date and identifier
* @throws IllegalArgumentException if the files contain a duplicate entry
*/
public static ImmutableListMultimap loadAllDates(
ResourceLocator groupsResource,
ResourceLocator settingsResource,
Collection curveValueResources) {
Collection curveCharSources = curveValueResources.stream().map(r -> r.getCharSource()).collect(toList());
return parse(d -> true, groupsResource.getCharSource(), settingsResource.getCharSource(), curveCharSources);
}
/**
* Parses one or more CSV format curve files for all available dates.
*
* A predicate is specified that is used to filter the dates that are returned.
* This could match a single date, a set of dates or all dates.
*
* If the files contain a duplicate entry an exception will be thrown.
*
* @param datePredicate the predicate used to select the dates
* @param groupsCharSource the curve groups CSV character source
* @param settingsCharSource the curve settings CSV character source
* @param curveValueCharSources the CSV character sources for curves
* @return the loaded curves, mapped by date and identifier
* @throws IllegalArgumentException if the files contain a duplicate entry
*/
public static ImmutableListMultimap parse(
Predicate datePredicate,
CharSource groupsCharSource,
CharSource settingsCharSource,
Collection curveValueCharSources) {
try {
Map, CurveName>> repoGroups = new LinkedHashMap<>();
Map, CurveName>> legalEntityGroups = new LinkedHashMap<>();
parseCurveMaps(groupsCharSource, repoGroups, legalEntityGroups);
Map> allCurves =
parseCurves(datePredicate, settingsCharSource, curveValueCharSources);
ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
for (Map.Entry> curveEntry : allCurves.entrySet()) {
LocalDate date = curveEntry.getKey();
Map curves = curveEntry.getValue();
for (Map.Entry, CurveName>> repoEntry : repoGroups.entrySet()) {
CurveGroupName groupName = repoEntry.getKey();
Map, Curve> repoCurves = MapStream.of(repoEntry.getValue())
.mapValues(name -> queryCurve(name, curves, date, groupName, "Repo"))
.toMap();
Map, Curve> issuerCurves = MapStream.of(legalEntityGroups.get(groupName))
.mapValues(name -> queryCurve(name, curves, date, groupName, "Issuer"))
.toMap();
builder.put(date, LegalEntityCurveGroup.of(groupName, repoCurves, issuerCurves));
}
}
return builder.build();
} catch (RuntimeException ex) {
throw new ParseFailureException(
ex, "Error parsing legal entity CSV files: {exceptionMessage}", ex.getMessage());
}
}
//-------------------------------------------------------------------------
private static Map> parseCurves(
Predicate datePredicate,
CharSource settingsResource,
Collection curvesResources) {
// load curve settings
Map settingsMap = parseCurveSettings(settingsResource);
// load curves, ensuring curves only be seen once within a date
Map> resultMap = new TreeMap<>();
for (CharSource curvesResource : curvesResources) {
Multimap fileCurvesByDate = parseSingle(datePredicate, curvesResource, settingsMap);
// Ensure curve names are unique, with a good error message
for (LocalDate date : fileCurvesByDate.keySet()) {
Collection fileCurves = fileCurvesByDate.get(date);
Map resultCurves = resultMap.computeIfAbsent(date, d -> new HashMap<>());
for (Curve fileCurve : fileCurves) {
if (resultCurves.put(fileCurve.getName(), fileCurve) != null) {
throw new ParseFailureException(
"Rates curve loader found multiple curves with the same name '{value}'", fileCurve.getName());
}
}
}
}
return resultMap;
}
private static Map parseCurveSettings(CharSource settingsResource) {
ImmutableMap.Builder builder = ImmutableMap.builder();
CsvFile csv = CsvFile.of(settingsResource, true);
for (CsvRow row : csv.rows()) {
String curveNameStr = row.getField(SETTINGS_CURVE_NAME);
String valueTypeStr = row.getField(SETTINGS_VALUE_TYPE);
String dayCountStr = row.getField(SETTINGS_DAY_COUNT);
String interpolatorStr = row.getField(SETTINGS_INTERPOLATOR);
String leftExtrapolatorStr = row.getField(SETTINGS_LEFT_EXTRAPOLATOR);
String rightExtrapolatorStr = row.getField(SETTINGS_RIGHT_EXTRAPOLATOR);
if (!VALUE_TYPE_MAP.containsKey(valueTypeStr.toLowerCase(Locale.ENGLISH))) {
throw new ParseFailureException(
"Unsupported {} in curve settings '{value}'", SETTINGS_VALUE_TYPE, valueTypeStr);
}
CurveName curveName = CurveName.of(curveNameStr);
ValueType valueType = VALUE_TYPE_MAP.get(valueTypeStr.toLowerCase(Locale.ENGLISH));
CurveInterpolator interpolator = CurveInterpolator.of(interpolatorStr);
CurveExtrapolator leftExtrap = CurveExtrapolator.of(leftExtrapolatorStr);
CurveExtrapolator rightExtrap = CurveExtrapolator.of(rightExtrapolatorStr);
// ONE_ONE day count is not used
DayCount dayCount = LoaderUtils.parseDayCount(dayCountStr);
LoadedCurveSettings settings = LoadedCurveSettings.of(
curveName, ValueType.YEAR_FRACTION, valueType, dayCount, interpolator, leftExtrap, rightExtrap);
builder.put(curveName, settings);
}
return builder.build();
}
private static Multimap parseSingle(
Predicate datePredicate,
CharSource curvesResource,
Map settingsMap) {
CsvFile csv = CsvFile.of(curvesResource, true);
Map> allNodes = new HashMap<>();
for (CsvRow row : csv.rows()) {
String dateStr = row.getField(CURVE_DATE);
String curveNameStr = row.getField(CURVE_NAME);
String pointDateStr = row.getField(CURVE_POINT_DATE);
String pointValueStr = row.getField(CURVE_POINT_VALUE);
String pointLabel = row.getField(CURVE_POINT_LABEL);
LocalDate date = LoaderUtils.parseDate(dateStr);
if (datePredicate.test(date)) {
LocalDate pointDate = LoaderUtils.parseDate(pointDateStr);
double pointValue = Double.valueOf(pointValueStr);
LoadedCurveKey key = LoadedCurveKey.of(date, CurveName.of(curveNameStr));
List curveNodes = allNodes.computeIfAbsent(key, k -> new ArrayList<>());
curveNodes.add(LoadedCurveNode.of(pointDate, pointValue, pointLabel));
}
}
return buildCurves(settingsMap, allNodes);
}
private static Multimap buildCurves(
Map settingsMap,
Map> allNodes) {
ImmutableMultimap.Builder results = ImmutableMultimap.builder();
for (Map.Entry> entry : allNodes.entrySet()) {
LoadedCurveKey key = entry.getKey();
LoadedCurveSettings settings = settingsMap.get(key.getCurveName());
if (settings == null) {
throw new ParseFailureException("Missing settings for curve '{value}'", key);
}
results.put(key.getCurveDate(), settings.createCurve(key.getCurveDate(), entry.getValue()));
}
return results.build();
}
//-------------------------------------------------------------------------
private static void parseCurveMaps(
CharSource groupsCharSource,
Map, CurveName>> repoGroups,
Map, CurveName>> legalEntityGroups) {
CsvFile csv = CsvFile.of(groupsCharSource, true);
for (CsvRow row : csv.rows()) {
String curveGroupStr = row.getField(GROUPS_NAME);
String curveTypeStr = row.getField(GROUPS_CURVE_TYPE);
String referenceStr = row.getField(GROUPS_REFERENCE);
String currencyStr = row.getField(GROUPS_CURRENCY);
String curveNameStr = row.getField(GROUPS_CURVE_NAME);
CurveName curveName = CurveName.of(curveNameStr);
createKey(
curveName,
CurveGroupName.of(curveGroupStr), curveTypeStr, referenceStr, currencyStr, repoGroups, legalEntityGroups);
}
}
private static void createKey(
CurveName curveName,
CurveGroupName curveGroup,
String curveTypeStr,
String referenceStr,
String currencyStr,
Map, CurveName>> repoGroups,
Map, CurveName>> legalEntityGroups) {
Currency currency = Currency.of(currencyStr);
if (REPO.equalsIgnoreCase(curveTypeStr.toLowerCase(Locale.ENGLISH))) {
RepoGroup repoGroup = RepoGroup.of(referenceStr);
repoGroups.computeIfAbsent(curveGroup, k -> new LinkedHashMap<>()).put(Pair.of(repoGroup, currency), curveName);
} else if (ISSUER.equalsIgnoreCase(curveTypeStr.toLowerCase(Locale.ENGLISH))) {
LegalEntityGroup legalEntiryGroup = LegalEntityGroup.of(referenceStr);
legalEntityGroups.computeIfAbsent(
curveGroup, k -> new LinkedHashMap<>()).put(Pair.of(legalEntiryGroup, currency), curveName);
} else {
throw new ParseFailureException("Unsupported curve type '{value}'", curveTypeStr);
}
}
//-------------------------------------------------------------------------
private static Curve queryCurve(
CurveName name,
Map curves,
LocalDate date,
CurveGroupName groupName,
String curveType) {
Curve curve = curves.get(name);
if (curve == null) {
throw new ParseFailureException(
"{curveType} curve values for '{curveName}' in group '{curveGroupName}' are missing on '{date}'",
curveType, name, groupName, date);
}
return curve;
}
//-------------------------------------------------------------------------
// restricted constructor
private LegalEntityRatesCurvesCsvLoader() {
}
}