com.opengamma.strata.loader.csv.RatesCurvesCsvLoader Maven / Gradle / Ivy
Show all versions of strata-loader Show documentation
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.loader.csv;
import static com.opengamma.strata.basics.date.DayCounts.ONE_ONE;
import static java.util.stream.Collectors.toList;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.basics.index.Index;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Decimal;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.io.CharSources;
import com.opengamma.strata.collect.io.CsvFile;
import com.opengamma.strata.collect.io.CsvOutput;
import com.opengamma.strata.collect.io.CsvRow;
import com.opengamma.strata.collect.io.ResourceLocator;
import com.opengamma.strata.collect.io.UnicodeBom;
import com.opengamma.strata.collect.result.ParseFailureException;
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.CurveInfoType;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.InterpolatedNodalCurve;
import com.opengamma.strata.market.curve.RatesCurveGroup;
import com.opengamma.strata.market.curve.RatesCurveGroupDefinition;
import com.opengamma.strata.market.curve.interpolator.CurveExtrapolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
import com.opengamma.strata.market.param.DatedParameterMetadata;
/**
* Loads a set of rates curves into memory by reading from CSV resources.
*
* There are three type of CSV files.
*
* The first file is the curve group metadata file.
* This file has the following header row:
* {@code Group Name, Curve Type, Reference, Curve Name}.
*
* - The 'Group Name' column is the name of the group of curves.
*
- The 'Curve Type' column is the type of the curve, "forward" or "discount".
*
- The 'Reference' column is the reference the curve is used for, such as "USD" or "USD-LIBOR-3M".
*
- 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' and extrapolator columns define the interpolator 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.
*
* CSV files sometimes contain a Unicode Byte Order Mark.
* Callers are responsible for handling this, such as by using {@link UnicodeBom}.
*/
public final class RatesCurvesCsvLoader {
// CSV column headers
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";
private static final ImmutableList HEADERS_SETTINGS = ImmutableList.of(
SETTINGS_CURVE_NAME,
SETTINGS_VALUE_TYPE,
SETTINGS_DAY_COUNT,
SETTINGS_INTERPOLATOR,
SETTINGS_LEFT_EXTRAPOLATOR,
SETTINGS_RIGHT_EXTRAPOLATOR);
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";
private static final ImmutableList HEADERS_NODES = ImmutableList.of(
CURVE_DATE, CURVE_NAME, CURVE_POINT_DATE, CURVE_POINT_VALUE, CURVE_POINT_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,
"forward", ValueType.FORWARD_RATE,
"priceindex", ValueType.PRICE_INDEX);
//-------------------------------------------------------------------------
/**
* 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) {
List curveGroups = RatesCurveGroupDefinitionCsvLoader.parseCurveGroupDefinitions(groupsCharSource);
Map> curves =
parseCurves(datePredicate, settingsCharSource, curveValueCharSources);
ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
for (RatesCurveGroupDefinition groupDefinition : curveGroups) {
for (Map.Entry> entry : curves.entrySet()) {
RatesCurveGroup curveGroup = RatesCurveGroup.ofCurves(groupDefinition, entry.getValue().values());
builder.put(entry.getKey(), curveGroup);
}
}
return builder.build();
}
//-------------------------------------------------------------------------
// loads the curves, filtering by date
private static Map> parseCurves(
Predicate datePredicate,
CharSource settingsResource,
Collection curvesResources) {
try {
// 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(
"Error parsing rates curves CSV files: Rates curve loader found multiple curves with the same name: '{value}'",
fileCurve.getName());
}
}
}
}
return resultMap;
} catch (ParseFailureException ex) {
throw ex;
} catch (RuntimeException ex) {
throw new ParseFailureException(ex, "Error parsing rates curves CSV files: {exceptionMessage}", ex.getMessage());
}
}
//-------------------------------------------------------------------------
// loads the curve settings CSV file
static Map parseCurveSettings(CharSource settingsResource) {
try {
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 yValueType = VALUE_TYPE_MAP.get(valueTypeStr.toLowerCase(Locale.ENGLISH));
CurveInterpolator interpolator = CurveInterpolator.of(interpolatorStr);
CurveExtrapolator leftExtrap = CurveExtrapolator.of(leftExtrapolatorStr);
CurveExtrapolator rightExtrap = CurveExtrapolator.of(rightExtrapolatorStr);
boolean isPriceIndex = yValueType.equals(ValueType.PRICE_INDEX);
ValueType xValueType = isPriceIndex ? ValueType.MONTHS : ValueType.YEAR_FRACTION;
DayCount dayCount = isPriceIndex ? ONE_ONE : LoaderUtils.parseDayCount(dayCountStr);
LoadedCurveSettings settings =
LoadedCurveSettings.of(curveName, xValueType, yValueType, dayCount, interpolator, leftExtrap, rightExtrap);
builder.put(curveName, settings);
}
return builder.build();
} catch (RuntimeException ex) {
throw new ParseFailureException(
ex, "Error parsing CSV file '{fileName}': {exceptionMessage}", CharSources.extractFileName(settingsResource), ex.getMessage());
}
}
//-------------------------------------------------------------------------
// loads a single curves CSV file
// requestedDate can be null, meaning load all dates
private static Multimap parseSingle(
Predicate datePredicate,
CharSource curvesResource,
Map settingsMap) {
try {
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);
} catch (RuntimeException ex) {
throw new ParseFailureException(
ex,
"Error parsing rates curve CSV file '{fileName}': {exceptionMessage}",
CharSources.extractFileName(curvesResource),
ex.getMessage());
}
}
// build the curves
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();
}
//-------------------------------------------------------------------------
/**
* Writes the curve settings in a CSV format to a file.
*
* @param file the file
* @param group the curve group
*/
public static void writeCurveSettings(File file, RatesCurveGroup group) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writeCurveSettings(writer, group);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Writes the curve settings in a CSV format to an appendable.
*
* @param underlying the underlying appendable destination
* @param group the curve group
*/
public static void writeCurveSettings(Appendable underlying, RatesCurveGroup group) {
CsvOutput csv = CsvOutput.standard(underlying);
// header
csv.writeLine(HEADERS_SETTINGS);
// rows
Map discountingCurves = group.getDiscountCurves();
Set names = new HashSet<>();
for (Entry entry : discountingCurves.entrySet()) {
Curve curve = entry.getValue();
csv.writeLine(curveSettings(curve));
names.add(curve.getName());
}
Map forwardCurves = group.getForwardCurves();
for (Entry entry : forwardCurves.entrySet()) {
Curve curve = entry.getValue();
if (!names.contains(curve.getName())) {
csv.writeLine(curveSettings(curve));
names.add(curve.getName());
}
}
}
private static List curveSettings(Curve curve) {
ArgChecker.isTrue(curve instanceof InterpolatedNodalCurve, "Curve must be an InterpolatedNodalCurve");
if (!VALUE_TYPE_MAP.inverse().containsKey(curve.getMetadata().getYValueType())) {
throw new IllegalArgumentException(
Messages.format("Unsupported ValueType in curve settings: {}", curve.getMetadata().getYValueType()));
}
InterpolatedNodalCurve interpolatedCurve = (InterpolatedNodalCurve) curve;
List line = new ArrayList<>();
line.add(curve.getName().getName());
line.add(VALUE_TYPE_MAP.inverse().get(curve.getMetadata().getYValueType()));
line.add(curve.getMetadata().getInfo(CurveInfoType.DAY_COUNT).toString());
line.add(interpolatedCurve.getInterpolator().toString());
line.add(interpolatedCurve.getExtrapolatorLeft().toString());
line.add(interpolatedCurve.getExtrapolatorRight().toString());
return line;
}
//-------------------------------------------------------------------------
/**
* Writes the curve groups definition in a CSV format to a file.
*
* @param file the file
* @param valuationDate the valuation date
* @param group the curve group
*/
public static void writeCurveNodes(File file, LocalDate valuationDate, RatesCurveGroup group) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writeCurveNodes(writer, valuationDate, group);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Writes the curve nodes in a CSV format to an appendable.
*
* @param underlying the underlying appendable destination
* @param valuationDate the valuation date
* @param group the curve group
*/
public static void writeCurveNodes(Appendable underlying, LocalDate valuationDate, RatesCurveGroup group) {
CsvOutput csv = CsvOutput.standard(underlying);
// header
csv.writeLine(HEADERS_NODES);
// rows
String valuationDateStr = valuationDate.toString();
Map discountingCurves = group.getDiscountCurves();
Set names = new HashSet<>();
for (Entry entry : discountingCurves.entrySet()) {
Curve curve = entry.getValue();
nodeLines(valuationDateStr, curve, csv);
names.add(curve.getName());
}
Map forwardCurves = group.getForwardCurves();
for (Entry entry : forwardCurves.entrySet()) {
Curve curve = entry.getValue();
if (!names.contains(curve.getName())) {
nodeLines(valuationDateStr, curve, csv);
names.add(curve.getName());
}
}
}
// add each node to the csv file
private static void nodeLines(String valuationDateStr, Curve curve, CsvOutput csv) {
ArgChecker.isTrue(curve instanceof InterpolatedNodalCurve, "interpolated");
InterpolatedNodalCurve interpolatedCurve = (InterpolatedNodalCurve) curve;
int nbPoints = interpolatedCurve.getXValues().size();
for (int i = 0; i < nbPoints; i++) {
ArgChecker.isTrue(
interpolatedCurve.getParameterMetadata(i) instanceof DatedParameterMetadata,
"Curve metadata must contain a date, but was " + interpolatedCurve.getParameterMetadata(i).getClass().getSimpleName());
DatedParameterMetadata metadata = (DatedParameterMetadata) interpolatedCurve.getParameterMetadata(i);
List line = new ArrayList<>();
line.add(valuationDateStr);
line.add(curve.getName().getName().toString());
line.add(metadata.getDate().toString());
line.add(Decimal.of(interpolatedCurve.getYValues().get(i)).formatAtLeast(1));
line.add(metadata.getLabel());
csv.writeLine(line);
}
}
//-------------------------------------------------------------------------
/**
* Restricted constructor.
*/
private RatesCurvesCsvLoader() {
}
}