/**
* 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.collect.Guavate.toImmutableList;
import static java.util.stream.Collectors.groupingBy;
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.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.CharSource;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.collect.MapStream;
import com.opengamma.strata.collect.Messages;
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.loader.LoaderUtils;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.curve.CurveGroup;
import com.opengamma.strata.market.curve.CurveGroupDefinition;
import com.opengamma.strata.market.curve.CurveGroupEntry;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.NodalCurveDefinition;
/**
* Loads a set of curve group definitions into memory by reading from CSV resources.
* <p>
* The CSV file has the following header row:<br />
* {@code Group Name, Curve Type, Reference, Curve Name}.
*
* <ul>
* <li>The 'Group Name' column is the name of the group of curves.
* <li>The 'Curve Type' column is the type of the curve, "forward" or "discount".
* <li>The 'Reference' column is the reference the curve is used for, such as "USD" or "USD-LIBOR-3M".
* <li>The 'Curve Name' column is the name of the curve.
* </ul>
*
* @see CurveGroupDefinition
*/
public final class CurveGroupDefinitionCsvLoader {
// Column headers
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_CURVE_NAME = "Curve Name";
private static final ImmutableList<String> HEADERS = ImmutableList.of(
GROUPS_NAME, GROUPS_CURVE_TYPE, GROUPS_REFERENCE, GROUPS_CURVE_NAME);
/** Name used in the reference column of the CSV file for discount curves. */
private static final String DISCOUNT = "discount";
/** Name used in the reference column of the CSV file for forward curves. */
private static final String FORWARD = "forward";
//-------------------------------------------------------------------------
/**
* Loads the curve groups definition CSV file.
* <p>
* The list of {@link NodalCurveDefinition} will be empty in the resulting definition.
*
* @param groupsResource the curve groups CSV resource
* @return the list of definitions
* @deprecated Use better named {@link #loadCurveGroupDefinitions(ResourceLocator)}
*/
@Deprecated
public static List<CurveGroupDefinition> loadCurveGroups(ResourceLocator groupsResource) {
return loadCurveGroupDefinitions(groupsResource);
}
/**
* Loads the curve groups definition CSV file.
* <p>
* The list of {@link NodalCurveDefinition} will be empty in the resulting definition.
*
* @param groupsResource the curve groups CSV resource
* @return the list of definitions
*/
public static List<CurveGroupDefinition> loadCurveGroupDefinitions(ResourceLocator groupsResource) {
return parseCurveGroupDefinitions(groupsResource.getCharSource());
}
//-------------------------------------------------------------------------
/**
* Parses the curve groups definition CSV file.
* <p>
* The list of {@link NodalCurveDefinition} will be empty in the resulting definition.
*
* @param groupsCharSource the curve groups CSV character source
* @return the list of definitions
*/
public static List<CurveGroupDefinition> parseCurveGroupDefinitions(CharSource groupsCharSource) {
Map<CurveName, Set<GroupAndReference>> curveGroups = new LinkedHashMap<>();
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 curveNameStr = row.getField(GROUPS_CURVE_NAME);
GroupAndReference gar = createKey(CurveGroupName.of(curveGroupStr), curveTypeStr, referenceStr);
CurveName curveName = CurveName.of(curveNameStr);
curveGroups.computeIfAbsent(curveName, k -> new LinkedHashSet<>()).add(gar);
}
return buildCurveGroups(curveGroups);
}
//-------------------------------------------------------------------------
// parses the identifier
private static GroupAndReference createKey(
CurveGroupName curveGroup,
String curveTypeStr,
String referenceStr) {
// discount and forward curves are supported
if (FORWARD.equalsIgnoreCase(curveTypeStr.toLowerCase(Locale.ENGLISH))) {
Index index = LoaderUtils.findIndex(referenceStr);
return new GroupAndReference(curveGroup, index);
} else if (DISCOUNT.equalsIgnoreCase(curveTypeStr.toLowerCase(Locale.ENGLISH))) {
Currency ccy = Currency.of(referenceStr);
return new GroupAndReference(curveGroup, ccy);
} else {
throw new IllegalArgumentException(Messages.format("Unsupported curve type: {}", curveTypeStr));
}
}
/**
* Builds a list of curve group definitions from the map of curves and their keys.
* <p>
* The keys specify which curve groups each curve belongs to and how it is used in the group, for example
* as a discount curve for a particular currency or as a forward curve for an index.
*
* @param garMap the map of name to keys
* @return a map of curve group name to curve group definition built from the curves
*/
private static ImmutableList<CurveGroupDefinition> buildCurveGroups(
Map<CurveName, Set<GroupAndReference>> garMap) {
Multimap<CurveGroupName, CurveGroupEntry> groups = LinkedHashMultimap.create();
for (Map.Entry<CurveName, Set<GroupAndReference>> entry : garMap.entrySet()) {
CurveName curveName = entry.getKey();
Set<GroupAndReference> curveIds = entry.getValue();
Map<CurveGroupName, List<GroupAndReference>> idsByGroup =
curveIds.stream().collect(groupingBy(p -> p.groupName));
for (Map.Entry<CurveGroupName, List<GroupAndReference>> groupEntry : idsByGroup.entrySet()) {
CurveGroupName groupName = groupEntry.getKey();
List<GroupAndReference> gars = groupEntry.getValue();
groups.put(groupName, curveGroupEntry(curveName, gars));
}
}
return MapStream.of(groups.asMap())
.map((name, entry) -> CurveGroupDefinition.of(name, entry, ImmutableList.of()))
.collect(toImmutableList());
}
/**
* Creates a curve group entry for a curve from a list of keys from the same curve group.
*
* @param curveName the name of the curve
* @param gars the group-reference pairs
* @return a curve group entry built from the data in the IDs
*/
private static CurveGroupEntry curveGroupEntry(CurveName curveName, List<GroupAndReference> gars) {
Set<Currency> currencies = new LinkedHashSet<>();
Set<Index> indices = new LinkedHashSet<>();
for (GroupAndReference gar : gars) {
if (gar.currency != null) {
currencies.add(gar.currency);
} else {
indices.add(gar.index);
}
}
return CurveGroupEntry.builder()
.curveName(curveName)
.discountCurrencies(currencies)
.indices(indices)
.build();
}
//-------------------------------------------------------------------------
/**
* Writes the curve groups definition in a CSV format to a file.
*
* @param file the destination for the CSV, such as a file
* @param groups the curve groups
*/
public static void writeCurveGroupDefinition(File file, CurveGroupDefinition... groups) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writeCurveGroupDefinition(writer, groups);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Writes the curve groups definition in a CSV format to an appendable.
*
* @param underlying the underlying appendable destination
* @param groups the curve groups
*/
public static void writeCurveGroupDefinition(Appendable underlying, CurveGroupDefinition... groups) {
CsvOutput csv = new CsvOutput(underlying);
csv.writeLine(HEADERS);
for (CurveGroupDefinition group : groups) {
writeCurveGroupDefinition(csv, group);
}
}
// write a single group definition to CSV
private static void writeCurveGroupDefinition(CsvOutput csv, CurveGroupDefinition group) {
String groupName = group.getName().getName();
for (CurveGroupEntry entry : group.getEntries()) {
for (Currency currency : entry.getDiscountCurrencies()) {
csv.writeLine(ImmutableList.of(groupName, DISCOUNT, currency.toString(), entry.getCurveName().getName()));
}
for (Index index : entry.getIndices()) {
csv.writeLine(ImmutableList.of(groupName, FORWARD, index.toString(), entry.getCurveName().getName()));
}
}
}
//-------------------------------------------------------------------------
/**
* Writes the curve group in a CSV format to a file.
*
* @param file the file
* @param groups the curve groups
*/
public static void writeCurveGroup(File file, CurveGroup... groups) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writeCurveGroup(writer, groups);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Writes the curve group in a CSV format to an appendable.
*
* @param underlying the underlying appendable destination
* @param groups the curve groups
*/
public static void writeCurveGroup(Appendable underlying, CurveGroup... groups) {
CsvOutput csv = new CsvOutput(underlying);
csv.writeLine(HEADERS);
for (CurveGroup group : groups) {
writeCurveGroup(csv, group);
}
}
// write a single group to CSV
private static void writeCurveGroup(CsvOutput csv, CurveGroup group) {
String groupName = group.getName().getName();
Map<Currency, Curve> discountingCurves = group.getDiscountCurves();
for (Entry<Currency, Curve> entry : discountingCurves.entrySet()) {
List<String> line = new ArrayList<>(4);
line.add(groupName);
line.add(DISCOUNT);
line.add(entry.getKey().toString());
line.add(entry.getValue().getName().getName());
csv.writeLine(line);
}
Map<Index, Curve> forwardCurves = group.getForwardCurves();
for (Entry<Index, Curve> entry : forwardCurves.entrySet()) {
List<String> line = new ArrayList<>(4);
line.add(groupName);
line.add(FORWARD);
line.add(entry.getKey().toString());
line.add(entry.getValue().getName().getName());
csv.writeLine(line);
}
}
//-------------------------------------------------------------------------
// This class only has static methods
private CurveGroupDefinitionCsvLoader() {
}
//-------------------------------------------------------------------------
// data holder
private static final class GroupAndReference {
private final CurveGroupName groupName;
private final Currency currency;
private final Index index;
private GroupAndReference(CurveGroupName groupName, Currency currency) {
this.groupName = groupName;
this.currency = currency;
this.index = null;
}
private GroupAndReference(CurveGroupName groupName, Index index) {
this.groupName = groupName;
this.currency = null;
this.index = index;
}
}
}