com.opengamma.strata.loader.csv.LegalEntityRatesCurvesCsvLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.loader.csv.LegalEntityRatesCurvesCsvLoader.java

Source

/*
 * 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.Messages;
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.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.
 * <p>
 * There are three type of CSV files.
 * <p>
 * The first file is the legal entity curve group metadata file.
 * This file has the following header row:<br />
 * {@code Group Name, Curve Type, Reference, Currency, 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, "repo" or "issuer".
 * <li>The 'Reference' column is the reference group for which the curve is used, legal entity group or repo group.
 * <li>The 'Currency' column is the reference currency for which the curve is used.
 * <li>The 'Curve Name' column is the name of the curve.
 * </ul>
 * <p>
 * The second file is the curve settings metadata file.
 * This file has the following header row:<br />
 * {@code Curve Name, Value Type, Day Count, Interpolator, Left Extrapolator, Right Extrapolator}.
 * <ul>
 * <li>The 'Curve Name' column is the name of the curve.
 * <li>The 'Value Type' column is the type of data in the curve, "zero" for zero rates, or "df" for discount factors.
 * <li>The 'Day Count' column is the name of the day count, such as "Act/365F".
 * <li>The 'Interpolator' column defines the interpolator to use.
 * <li>The 'Left Extrapolator' and 'Right Extrapolator' columns define the extrapolators to use.
 * </ul>
 * <p>
 * The third file is the curve values file.
 * This file has the following header row:<br />
 * {@code Valuation Date, Curve Name, Date, Value, Label}.
 * <ul>
 * <li>The 'Valuation Date' column provides the valuation date, allowing data from different
 *  days to be stored in the same file
 * <li>The 'Curve Name' column is the name of the curve.
 * <li>The 'Date' column is the date associated with the node.
 * <li>The 'Value' column is value of the curve at the date.
 * <li>The 'Label' column is the label used to refer to the node.
 * </ul>
 * <p>
 * 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<String, ValueType> 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.
     * <p>
     * Only those quotes that match the specified date will be loaded.
     * <p>
     * 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<LegalEntityCurveGroup> load(LocalDate marketDataDate,
            ResourceLocator groupsResource, ResourceLocator settingsResource,
            Collection<ResourceLocator> curveValueResources) {

        Collection<CharSource> curveCharSources = curveValueResources.stream().map(r -> r.getCharSource())
                .collect(toList());
        ListMultimap<LocalDate, LegalEntityCurveGroup> 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.
     * <p>
     * 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<LocalDate, LegalEntityCurveGroup> loadAllDates(
            ResourceLocator groupsResource, ResourceLocator settingsResource,
            Collection<ResourceLocator> curveValueResources) {

        Collection<CharSource> 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.
     * <p>
     * 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.
     * <p>
     * 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<LocalDate, LegalEntityCurveGroup> parse(Predicate<LocalDate> datePredicate,
            CharSource groupsCharSource, CharSource settingsCharSource,
            Collection<CharSource> curveValueCharSources) {

        Map<CurveGroupName, Map<Pair<RepoGroup, Currency>, CurveName>> repoGroups = new LinkedHashMap<>();
        Map<CurveGroupName, Map<Pair<LegalEntityGroup, Currency>, CurveName>> legalEntityGroups = new LinkedHashMap<>();
        parseCurveMaps(groupsCharSource, repoGroups, legalEntityGroups);
        Map<LocalDate, Map<CurveName, Curve>> allCurves = parseCurves(datePredicate, settingsCharSource,
                curveValueCharSources);
        ImmutableListMultimap.Builder<LocalDate, LegalEntityCurveGroup> builder = ImmutableListMultimap.builder();

        for (Map.Entry<LocalDate, Map<CurveName, Curve>> curveEntry : allCurves.entrySet()) {
            LocalDate date = curveEntry.getKey();
            Map<CurveName, Curve> curves = curveEntry.getValue();
            for (Map.Entry<CurveGroupName, Map<Pair<RepoGroup, Currency>, CurveName>> repoEntry : repoGroups
                    .entrySet()) {
                CurveGroupName groupName = repoEntry.getKey();
                Map<Pair<RepoGroup, Currency>, Curve> repoCurves = MapStream.of(repoEntry.getValue())
                        .mapValues(name -> queryCurve(name, curves, date, groupName, "Repo")).toMap();
                Map<Pair<LegalEntityGroup, Currency>, 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();
    }

    //-------------------------------------------------------------------------
    private static Map<LocalDate, Map<CurveName, Curve>> parseCurves(Predicate<LocalDate> datePredicate,
            CharSource settingsResource, Collection<CharSource> curvesResources) {

        // load curve settings
        Map<CurveName, LoadedCurveSettings> settingsMap = parseCurveSettings(settingsResource);

        // load curves, ensuring curves only be seen once within a date
        Map<LocalDate, Map<CurveName, Curve>> resultMap = new TreeMap<>();
        for (CharSource curvesResource : curvesResources) {
            Multimap<LocalDate, Curve> fileCurvesByDate = parseSingle(datePredicate, curvesResource, settingsMap);
            // Ensure curve names are unique, with a good error message
            for (LocalDate date : fileCurvesByDate.keySet()) {
                Collection<Curve> fileCurves = fileCurvesByDate.get(date);
                Map<CurveName, Curve> resultCurves = resultMap.computeIfAbsent(date, d -> new HashMap<>());
                for (Curve fileCurve : fileCurves) {
                    if (resultCurves.put(fileCurve.getName(), fileCurve) != null) {
                        throw new IllegalArgumentException(
                                "Rates curve loader found multiple curves with the same name: "
                                        + fileCurve.getName());
                    }
                }
            }
        }
        return resultMap;
    }

    private static Map<CurveName, LoadedCurveSettings> parseCurveSettings(CharSource settingsResource) {
        ImmutableMap.Builder<CurveName, LoadedCurveSettings> 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 IllegalArgumentException(
                        Messages.format("Unsupported {} in curve settings: {}", 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<LocalDate, Curve> parseSingle(Predicate<LocalDate> datePredicate,
            CharSource curvesResource, Map<CurveName, LoadedCurveSettings> settingsMap) {

        CsvFile csv = CsvFile.of(curvesResource, true);
        Map<LoadedCurveKey, List<LoadedCurveNode>> 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<LoadedCurveNode> curveNodes = allNodes.computeIfAbsent(key, k -> new ArrayList<>());
                curveNodes.add(LoadedCurveNode.of(pointDate, pointValue, pointLabel));
            }
        }
        return buildCurves(settingsMap, allNodes);
    }

    private static Multimap<LocalDate, Curve> buildCurves(Map<CurveName, LoadedCurveSettings> settingsMap,
            Map<LoadedCurveKey, List<LoadedCurveNode>> allNodes) {

        ImmutableMultimap.Builder<LocalDate, Curve> results = ImmutableMultimap.builder();

        for (Map.Entry<LoadedCurveKey, List<LoadedCurveNode>> entry : allNodes.entrySet()) {
            LoadedCurveKey key = entry.getKey();
            LoadedCurveSettings settings = settingsMap.get(key.getCurveName());

            if (settings == null) {
                throw new IllegalArgumentException(Messages.format("Missing settings for curve: {}", key));
            }
            results.put(key.getCurveDate(), settings.createCurve(key.getCurveDate(), entry.getValue()));
        }
        return results.build();
    }

    //-------------------------------------------------------------------------
    private static void parseCurveMaps(CharSource groupsCharSource,
            Map<CurveGroupName, Map<Pair<RepoGroup, Currency>, CurveName>> repoGroups,
            Map<CurveGroupName, Map<Pair<LegalEntityGroup, Currency>, 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<CurveGroupName, Map<Pair<RepoGroup, Currency>, CurveName>> repoGroups,
            Map<CurveGroupName, Map<Pair<LegalEntityGroup, Currency>, 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 IllegalArgumentException(Messages.format("Unsupported curve type: {}", curveTypeStr));
        }
    }

    //-------------------------------------------------------------------------
    private static Curve queryCurve(CurveName name, Map<CurveName, Curve> curves, LocalDate date,
            CurveGroupName groupName, String curveType) {

        Curve curve = curves.get(name);
        if (curve == null) {
            throw new IllegalArgumentException(curveType + " curve values for " + name.toString() + " in group "
                    + groupName.getName() + " are missing on " + date.toString());
        }
        return curve;
    }

}