/**
* 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() {
}
}