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

Java tutorial

Introduction

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

Source

/**
 * 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 com.opengamma.strata.collect.Guavate.toImmutableMap;
import static java.util.stream.Collectors.toList;

import java.time.LocalDate;
import java.time.Period;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.CharSource;
import com.google.common.math.DoubleMath;
import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.date.Tenor;
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.data.FieldName;
import com.opengamma.strata.market.curve.CurveGroupDefinition;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.CurveNode;
import com.opengamma.strata.market.curve.CurveNodeClashAction;
import com.opengamma.strata.market.curve.CurveNodeDate;
import com.opengamma.strata.market.curve.CurveNodeDateOrder;
import com.opengamma.strata.market.curve.NodalCurveDefinition;
import com.opengamma.strata.market.curve.node.FixedIborSwapCurveNode;
import com.opengamma.strata.market.curve.node.FixedInflationSwapCurveNode;
import com.opengamma.strata.market.curve.node.FixedOvernightSwapCurveNode;
import com.opengamma.strata.market.curve.node.FraCurveNode;
import com.opengamma.strata.market.curve.node.FxSwapCurveNode;
import com.opengamma.strata.market.curve.node.IborFixingDepositCurveNode;
import com.opengamma.strata.market.curve.node.IborFutureCurveNode;
import com.opengamma.strata.market.curve.node.IborIborSwapCurveNode;
import com.opengamma.strata.market.curve.node.OvernightIborSwapCurveNode;
import com.opengamma.strata.market.curve.node.TermDepositCurveNode;
import com.opengamma.strata.market.curve.node.ThreeLegBasisSwapCurveNode;
import com.opengamma.strata.market.curve.node.XCcyIborIborSwapCurveNode;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.product.deposit.type.IborFixingDepositConvention;
import com.opengamma.strata.product.deposit.type.IborFixingDepositTemplate;
import com.opengamma.strata.product.deposit.type.TermDepositConvention;
import com.opengamma.strata.product.deposit.type.TermDepositTemplate;
import com.opengamma.strata.product.fra.type.FraConvention;
import com.opengamma.strata.product.fra.type.FraTemplate;
import com.opengamma.strata.product.fx.type.FxSwapConvention;
import com.opengamma.strata.product.fx.type.FxSwapTemplate;
import com.opengamma.strata.product.index.type.IborFutureConvention;
import com.opengamma.strata.product.index.type.IborFutureTemplate;
import com.opengamma.strata.product.swap.type.FixedIborSwapConvention;
import com.opengamma.strata.product.swap.type.FixedIborSwapTemplate;
import com.opengamma.strata.product.swap.type.FixedInflationSwapConvention;
import com.opengamma.strata.product.swap.type.FixedInflationSwapTemplate;
import com.opengamma.strata.product.swap.type.FixedOvernightSwapConvention;
import com.opengamma.strata.product.swap.type.FixedOvernightSwapTemplate;
import com.opengamma.strata.product.swap.type.IborIborSwapConvention;
import com.opengamma.strata.product.swap.type.IborIborSwapTemplate;
import com.opengamma.strata.product.swap.type.OvernightIborSwapConvention;
import com.opengamma.strata.product.swap.type.OvernightIborSwapTemplate;
import com.opengamma.strata.product.swap.type.ThreeLegBasisSwapConvention;
import com.opengamma.strata.product.swap.type.ThreeLegBasisSwapTemplate;
import com.opengamma.strata.product.swap.type.XCcyIborIborSwapConvention;
import com.opengamma.strata.product.swap.type.XCcyIborIborSwapTemplate;

/**
 * Loads a set of definitions to calibrate rates curves by reading from CSV resources.
 * <p>
 * There are three type of CSV files.
 * <p>
 * The first file is the curve group metadata file.
 * This 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>
 * <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' and extrapolator columns define the interpolator to use.
 * </ul>
 * <p>
 * The third file is the curve calibration nodes file.
 * This file has the following header row:<br />
 * {@code Curve Name,Label,Symbology,Ticker,Field Name,Type,Convention,Time,Date,Min Gap,Clash Action,Spread}.
 * <ul>
 * <li>The 'Curve Name' column is the name of the curve.
 * <li>The 'Label' column is the label used to refer to the node.
 * <li>The 'Symbology' column is the symbology scheme applicable to the ticker used for the market price.
 * <li>The 'Ticker' column is the identifier within the symbology used for the market price.
 * <li>The 'Field Name' column is the field name used for the market price, defaulted to "MarketValue", allowing
 *  fields such as 'Bid' or 'Ask' to be specified.
 * <li>The 'Type' column is the type of the instrument, such as "FRA" or "OIS".
 * <li>The 'Convention' column is the name of the convention to use.
 * <li>The 'Time' column is the description of the time, such as "1Y" for a 1 year swap, or "3Mx6M" for a FRA.
 * <li>The optional 'Date' column is the date to use for the node, defaults to "End", but can be
 *  set to "LastFixing" or a yyyy-MM-dd date.
 * <li>The optional 'Min Gap' column is the minimum gap between this node and the adjacent nodes.
 * <li>The optional 'Clash Action' column is the action to perform if the nodes are closer than the minimum gap
 *  or in the wrong order, defaults to "Exception", but can be set to "DropThis" or "DropOther".
 * <li>The optional 'Spread' column is the spread to add to the instrument.
 * </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.
 */
public final class RatesCalibrationCsvLoader {

    // CSV column headers
    private static final String CURVE_NAME = "Curve Name";
    private static final String CURVE_LABEL = "Label";
    private static final String CURVE_SYMBOLOGY_QUOTE = "Symbology";
    private static final String CURVE_TICKER_QUOTE = "Ticker";
    private static final String CURVE_FIELD_QUOTE = "Field Name";
    private static final String CURVE_TYPE = "Type";
    private static final String CURVE_CONVENTION = "Convention";
    private static final String CURVE_TIME = "Time";
    private static final String CURVE_DATE = "Date";
    private static final String CURVE_SPREAD = "Spread";
    private static final String CURVE_MIN_GAP = "Min Gap";
    private static final String CURVE_CLASH_ACTION = "Clash Action";

    // Regex to parse FRA time string
    private static final Pattern FRA_TIME_REGEX = Pattern.compile("P?([0-9]+)M? ?X ?P?([0-9]+)M?");
    // Regex to parse future time string
    private static final Pattern FUT_TIME_REGEX = Pattern
            .compile("P?((?:[0-9]+D)?(?:[0-9]+W)?(?:[0-9]+M)?) ?[+] ?([0-9]+)");
    // Regex to parse future month string
    private static final Pattern FUT_MONTH_REGEX = Pattern.compile("([A-Z][A-Z][A-Z][0-9][0-9])");
    // Regex to parse simple time string with years, months and days
    private static final Pattern SIMPLE_YMD_TIME_REGEX = Pattern
            .compile("P?(([0-9]+Y)?([0-9]+M)?([0-9]+W)?([0-9]+D)?)");
    // Regex to parse simple time string with years and months
    private static final Pattern SIMPLE_YM_TIME_REGEX = Pattern.compile("P?(([0-9]+Y)?([0-9]+M)?)");
    // Regex to parse simple time string with days
    private static final Pattern SIMPLE_DAYS_REGEX = Pattern.compile("P?([0-9]+D)?");
    // parse year-month
    private static final DateTimeFormatter YM_FORMATTER = new DateTimeFormatterBuilder().parseCaseInsensitive()
            .appendPattern("MMMuu").toFormatter(Locale.ENGLISH);

    //-------------------------------------------------------------------------
    /**
     * Loads one or more CSV format curve calibration files.
     * <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 curveNodeResources  the CSV resources for curve nodes
     * @return the group definitions, mapped by name
     * @throws IllegalArgumentException if the files contain a duplicate entry
     */
    public static ImmutableMap<CurveGroupName, CurveGroupDefinition> load(ResourceLocator groupsResource,
            ResourceLocator settingsResource, ResourceLocator... curveNodeResources) {

        return load(groupsResource, settingsResource, ImmutableList.copyOf(curveNodeResources));
    }

    /**
     * Loads one or more CSV format curve calibration files.
     * <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 curveNodeResources  the CSV resources for curve nodes
     * @return the group definitions, mapped by name
     * @throws IllegalArgumentException if the files contain a duplicate entry
     */
    public static ImmutableMap<CurveGroupName, CurveGroupDefinition> load(ResourceLocator groupsResource,
            ResourceLocator settingsResource, Collection<ResourceLocator> curveNodeResources) {

        Collection<CharSource> curveNodeCharSources = curveNodeResources.stream().map(r -> r.getCharSource())
                .collect(toList());
        return parse(groupsResource.getCharSource(), settingsResource.getCharSource(), curveNodeCharSources);
    }

    //-------------------------------------------------------------------------
    /**
     * Parses one or more CSV format curve calibration files.
     * <p>
     * If the files contain a duplicate entry an exception will be thrown.
     * 
     * @param groupsCharSource  the curve groups CSV character source
     * @param settingsCharSource  the curve settings CSV character source
     * @param curveNodeCharSources  the CSV character sources for curve nodes
     * @return the group definitions, mapped by name
     * @throws IllegalArgumentException if the files contain a duplicate entry
     */
    public static ImmutableMap<CurveGroupName, CurveGroupDefinition> parse(CharSource groupsCharSource,
            CharSource settingsCharSource, Collection<CharSource> curveNodeCharSources) {

        // load curve groups and settings
        List<CurveGroupDefinition> curveGroups = CurveGroupDefinitionCsvLoader
                .parseCurveGroupDefinitions(groupsCharSource);
        Map<CurveName, LoadedCurveSettings> settingsMap = RatesCurvesCsvLoader
                .parseCurveSettings(settingsCharSource);

        // load curve definitions
        List<NodalCurveDefinition> curveDefinitions = curveNodeCharSources.stream()
                .flatMap(res -> parseSingle(res, settingsMap).stream()).collect(toImmutableList());

        // Add the curve definitions to the curve group definitions
        return curveGroups.stream().map(groupDefinition -> groupDefinition.withCurveDefinitions(curveDefinitions))
                .collect(toImmutableMap(groupDefinition -> groupDefinition.getName()));
    }

    //-------------------------------------------------------------------------
    // loads a single curves CSV file
    // requestedDate can be null, meaning load all dates
    private static List<NodalCurveDefinition> parseSingle(CharSource resource,
            Map<CurveName, LoadedCurveSettings> settingsMap) {

        CsvFile csv = CsvFile.of(resource, true);
        Map<CurveName, List<CurveNode>> allNodes = new HashMap<>();
        for (CsvRow row : csv.rows()) {
            String curveNameStr = row.getField(CURVE_NAME);
            String label = row.getField(CURVE_LABEL);
            String symbologyQuoteStr = row.getField(CURVE_SYMBOLOGY_QUOTE);
            String tickerQuoteStr = row.getField(CURVE_TICKER_QUOTE);
            String fieldQuoteStr = row.getField(CURVE_FIELD_QUOTE);
            String typeStr = row.getField(CURVE_TYPE);
            String conventionStr = row.getField(CURVE_CONVENTION);
            String timeStr = row.getField(CURVE_TIME);
            String dateStr = row.findField(CURVE_DATE).orElse("");
            String minGapStr = row.findField(CURVE_MIN_GAP).orElse("");
            String clashActionStr = row.findField(CURVE_CLASH_ACTION).orElse("");
            String spreadStr = row.findField(CURVE_SPREAD).orElse("");

            CurveName curveName = CurveName.of(curveNameStr);
            StandardId quoteStandardId = StandardId.of(symbologyQuoteStr, tickerQuoteStr);
            FieldName quoteField = fieldQuoteStr.isEmpty() ? FieldName.MARKET_VALUE : FieldName.of(fieldQuoteStr);
            QuoteId quoteId = QuoteId.of(quoteStandardId, quoteField);
            double spread = spreadStr.isEmpty() ? 0d : Double.parseDouble(spreadStr);
            CurveNodeDate date = parseDate(dateStr);
            CurveNodeDateOrder order = parseDateOrder(minGapStr, clashActionStr);

            List<CurveNode> curveNodes = allNodes.computeIfAbsent(curveName, k -> new ArrayList<>());
            curveNodes.add(createCurveNode(typeStr, conventionStr, timeStr, label, quoteId, spread, date, order));
        }
        return buildCurveDefinition(settingsMap, allNodes);
    }

    // parse date order
    private static CurveNodeDate parseDate(String dateStr) {
        if (dateStr.isEmpty()) {
            return CurveNodeDate.END;
        }
        if (dateStr.length() == 10 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-') {
            return CurveNodeDate.of(LocalDate.parse(dateStr));
        }
        String dateUpper = dateStr.toUpperCase(Locale.ENGLISH);
        if (dateUpper.equals("END")) {
            return CurveNodeDate.END;
        }
        if (dateUpper.equals("LASTFIXING")) {
            return CurveNodeDate.LAST_FIXING;
        }
        throw new IllegalArgumentException(Messages.format(
                "Invalid format for node date, should be date in 'yyyy-MM-dd' format, 'End' or 'LastFixing': {}",
                dateUpper));
    }

    // parse date order
    private static CurveNodeDateOrder parseDateOrder(String minGapStr, String clashActionStr) {
        CurveNodeClashAction clashAction = clashActionStr.isEmpty() ? CurveNodeClashAction.EXCEPTION
                : CurveNodeClashAction.of(clashActionStr);
        if (minGapStr.isEmpty()) {
            return CurveNodeDateOrder.of(1, clashAction);
        }
        Matcher matcher = SIMPLE_DAYS_REGEX.matcher(minGapStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid days format for minimum gap, should be 2D or P2D: {}", minGapStr));
        }
        Period minGap = Period.parse("P" + matcher.group(1));
        return CurveNodeDateOrder.of(minGap.getDays(), clashAction);
    }

    // build the curves
    private static List<NodalCurveDefinition> buildCurveDefinition(Map<CurveName, LoadedCurveSettings> settingsMap,
            Map<CurveName, List<CurveNode>> allNodes) {

        ImmutableList.Builder<NodalCurveDefinition> results = ImmutableList.builder();

        for (Map.Entry<CurveName, List<CurveNode>> entry : allNodes.entrySet()) {
            CurveName name = entry.getKey();
            LoadedCurveSettings settings = settingsMap.get(name);

            if (settings == null) {
                throw new IllegalArgumentException(Messages.format("Missing settings for curve: {}", name));
            }
            results.add(settings.createCurveDefinition(entry.getValue()));
        }
        return results.build();
    }

    //-------------------------------------------------------------------------
    // create the curve node
    private static CurveNode createCurveNode(String typeStr, String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        if ("DEP".equalsIgnoreCase(typeStr) || "TermDeposit".equalsIgnoreCase(typeStr)) {
            return curveTermDepositCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("FIX".equalsIgnoreCase(typeStr) || "IborFixingDeposit".equalsIgnoreCase(typeStr)) {
            return curveIborFixingDepositCurveNode(conventionStr, label, quoteId, spread, date, order);
        }
        if ("FRA".equalsIgnoreCase(typeStr)) {
            return curveFraCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("IFU".equalsIgnoreCase(typeStr) || "IborFuture".equalsIgnoreCase(typeStr)) {
            return curveIborFutureCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("OIS".equalsIgnoreCase(typeStr) || "FixedOvernightSwap".equalsIgnoreCase(typeStr)) {
            return curveFixedOvernightCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("IRS".equalsIgnoreCase(typeStr) || "FixedIborSwap".equalsIgnoreCase(typeStr)) {
            return curveFixedIborCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("BAS".equalsIgnoreCase(typeStr) || "IborIborSwap".equalsIgnoreCase(typeStr)) {
            return curveIborIborCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("BS3".equalsIgnoreCase(typeStr) || "ThreeLegBasisSwap".equalsIgnoreCase(typeStr)) {
            return curveThreeLegBasisCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("ONI".equalsIgnoreCase(typeStr) || "OvernightIborBasisSwap".equalsIgnoreCase(typeStr)) {
            return curveOvernightIborCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("XCS".equalsIgnoreCase(typeStr) || "XCcyIborIborSwap".equalsIgnoreCase(typeStr)) {
            return curveXCcyIborIborCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("FXS".equalsIgnoreCase(typeStr) || "FxSwap".equalsIgnoreCase(typeStr)) {
            return curveFxSwapCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        if ("INF".equalsIgnoreCase(typeStr) || "FixedInflationSwap".equalsIgnoreCase(typeStr)) {
            return curveFixedInflationCurveNode(conventionStr, timeStr, label, quoteId, spread, date, order);
        }
        throw new IllegalArgumentException(Messages.format("Invalid curve node type: {}", typeStr));
    }

    private static CurveNode curveTermDepositCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YMD_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Term Deposit: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        TermDepositConvention convention = TermDepositConvention.of(conventionStr);
        TermDepositTemplate template = TermDepositTemplate.of(periodToEnd, convention);
        return TermDepositCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveIborFixingDepositCurveNode(String conventionStr, String label, QuoteId quoteId,
            double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        IborFixingDepositConvention convention = IborFixingDepositConvention.of(conventionStr);
        IborFixingDepositTemplate template = IborFixingDepositTemplate
                .of(convention.getIndex().getTenor().getPeriod(), convention);
        return IborFixingDepositCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveFraCurveNode(String conventionStr, String timeStr, String label, QuoteId quoteId,
            double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = FRA_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(Messages.format("Invalid time format for FRA: {}", timeStr));
        }
        Period periodToStart = Period.parse("P" + matcher.group(1) + "M");
        Period periodToEnd = Period.parse("P" + matcher.group(2) + "M");

        FraConvention convention = FraConvention.of(conventionStr);
        FraTemplate template = FraTemplate.of(periodToStart, periodToEnd, convention);
        return FraCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread).label(label)
                .date(date).dateOrder(order).build();
    }

    private static CurveNode curveIborFutureCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = FUT_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (matcher.matches()) {
            Period periodToStart = Period.parse("P" + matcher.group(1));
            int sequenceNumber = Integer.parseInt(matcher.group(2));
            IborFutureConvention convention = IborFutureConvention.of(conventionStr);
            IborFutureTemplate template = IborFutureTemplate.of(periodToStart, sequenceNumber, convention);
            return IborFutureCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                    .label(label).date(date).dateOrder(order).build();
        }
        Matcher matcher2 = FUT_MONTH_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (matcher2.matches()) {
            YearMonth yearMonth = YearMonth.parse(matcher2.group(1), YM_FORMATTER);
            IborFutureConvention convention = IborFutureConvention.of(conventionStr);
            IborFutureTemplate template = IborFutureTemplate.of(yearMonth, convention);
            return IborFutureCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                    .label(label).date(date).dateOrder(order).build();
        }
        throw new IllegalArgumentException(Messages.format("Invalid time format for Ibor Future: {}", timeStr));
    }

    //-------------------------------------------------------------------------
    private static CurveNode curveFixedOvernightCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YMD_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Fixed-Overnight swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        FixedOvernightSwapConvention convention = FixedOvernightSwapConvention.of(conventionStr);
        FixedOvernightSwapTemplate template = FixedOvernightSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return FixedOvernightSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveFixedIborCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YM_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Fixed-Ibor swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        FixedIborSwapConvention convention = FixedIborSwapConvention.of(conventionStr);
        FixedIborSwapTemplate template = FixedIborSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return FixedIborSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveIborIborCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YM_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Ibor-Ibor swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        IborIborSwapConvention convention = IborIborSwapConvention.of(conventionStr);
        IborIborSwapTemplate template = IborIborSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return IborIborSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveThreeLegBasisCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YM_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Three legs basis swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        ThreeLegBasisSwapConvention convention = ThreeLegBasisSwapConvention.of(conventionStr);
        ThreeLegBasisSwapTemplate template = ThreeLegBasisSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return ThreeLegBasisSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveXCcyIborIborCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YM_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Cross Currency Swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        XCcyIborIborSwapConvention convention = XCcyIborIborSwapConvention.of(conventionStr);
        XCcyIborIborSwapTemplate template = XCcyIborIborSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return XCcyIborIborSwapCurveNode.builder().template(template).spreadId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveOvernightIborCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YMD_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Overnight-Ibor swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        OvernightIborSwapConvention convention = OvernightIborSwapConvention.of(conventionStr);
        OvernightIborSwapTemplate template = OvernightIborSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return OvernightIborSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    private static CurveNode curveFxSwapCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        if (!DoubleMath.fuzzyEquals(spread, 0d, 1e-10d)) {
            throw new IllegalArgumentException("Additional spread must be zero for FX swaps");
        }
        Matcher matcher = SIMPLE_YMD_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(Messages.format("Invalid time format for FX swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        FxSwapConvention convention = FxSwapConvention.of(conventionStr);
        FxSwapTemplate template = FxSwapTemplate.of(periodToEnd, convention);
        return FxSwapCurveNode.builder().template(template).farForwardPointsId(quoteId).label(label).date(date)
                .dateOrder(order).build();
    }

    private static CurveNode curveFixedInflationCurveNode(String conventionStr, String timeStr, String label,
            QuoteId quoteId, double spread, CurveNodeDate date, CurveNodeDateOrder order) {

        Matcher matcher = SIMPLE_YM_TIME_REGEX.matcher(timeStr.toUpperCase(Locale.ENGLISH));
        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    Messages.format("Invalid time format for Fixed-Inflation swap: {}", timeStr));
        }
        Period periodToEnd = Period.parse("P" + matcher.group(1));
        FixedInflationSwapConvention convention = FixedInflationSwapConvention.of(conventionStr);
        FixedInflationSwapTemplate template = FixedInflationSwapTemplate.of(Tenor.of(periodToEnd), convention);
        return FixedInflationSwapCurveNode.builder().template(template).rateId(quoteId).additionalSpread(spread)
                .label(label).date(date).dateOrder(order).build();
    }

    //-------------------------------------------------------------------------
    /**
     * Restricted constructor.
     */
    private RatesCalibrationCsvLoader() {
    }

}