/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.examples.marketdata.credit.markit;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.CharSource;
import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.io.CsvFile;
import com.opengamma.strata.collect.io.CsvRow;
import com.opengamma.strata.data.ImmutableMarketDataBuilder;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.pricer.credit.CdsRecoveryRate;
import com.opengamma.strata.pricer.credit.IsdaCreditCurveInputs;
import com.opengamma.strata.pricer.credit.IsdaIndexCreditCurveInputsId;
import com.opengamma.strata.pricer.credit.IsdaIndexRecoveryRateId;
import com.opengamma.strata.product.credit.IndexReferenceInformation;
import com.opengamma.strata.product.credit.type.CdsConvention;
/**
* Parser to load daily index curve information provided by Markit.
* <p>
* The columns are defined as {@code
* Date,Name,Series,Version,Term,
* RED Code,Index ID,Maturity,On The Run,Composite Price,
* Composite Spread,Model Price,Model Spread,Depth,Heat}.
* <p>
* Also reads static data from a csv file
* <p>
* RedCode,From Date,Convention,Recovery Rate,Index Factor
*/
public class MarkitIndexCreditCurveDataParser {
// Markit date format with the month in full caps. e.g. 11-JUL-14
private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder()
.parseCaseInsensitive().appendPattern("dd-MMM-uu").toFormatter(Locale.ENGLISH);
enum Columns {
Series("Series"),
Version("Version"),
Term("Term"),
RedCode("RED Code"),
Maturity("Maturity"),
CompositeSpread("Composite Spread"),
ModelSpread("Model Spread");
private final String columnName;
Columns(String columnName) {
this.columnName = columnName;
}
public String getColumnName() {
return columnName;
}
}
/**
* Parses the specified sources.
*
* @param builder the market data builder that the resulting curve and recovery rate items should be loaded into
* @param curveSource the source of curve data to parse
* @param staticDataSource the source of static data to parse
*/
public static void parse(
ImmutableMarketDataBuilder builder,
CharSource curveSource,
CharSource staticDataSource) {
Map<IsdaIndexCreditCurveInputsId, List<Point>> curveData = Maps.newHashMap();
Map<MarkitRedCode, StaticData> staticDataMap = parseStaticData(staticDataSource);
CsvFile csv = CsvFile.of(curveSource, true);
for (CsvRow row : csv.rows()) {
String seriesText = row.getField(Columns.Series.getColumnName());
String versionText = row.getField(Columns.Version.getColumnName());
String termText = row.getField(Columns.Term.getColumnName());
String redCodeText = row.getField(Columns.RedCode.getColumnName());
String maturityText = row.getField(Columns.Maturity.getColumnName());
String compositeSpreadText = row.getField(Columns.CompositeSpread.getColumnName());
String modelSpreadText = row.getField(Columns.ModelSpread.getColumnName());
StandardId indexId = MarkitRedCode.id(redCodeText);
int indexSeries = Integer.parseInt(seriesText);
int indexAnnexVersion = Integer.parseInt(versionText);
IsdaIndexCreditCurveInputsId id = IsdaIndexCreditCurveInputsId.of(
IndexReferenceInformation.of(
indexId,
indexSeries,
indexAnnexVersion));
Tenor term = Tenor.parse(termText);
LocalDate maturity = LocalDate.parse(maturityText, DATE_FORMAT);
double spread;
if (compositeSpreadText.isEmpty()) {
if (modelSpreadText.isEmpty()) {
// there is no rate for this row, continue
continue;
}
// fall back to the model rate is the composite is missing
spread = parseRate(modelSpreadText);
} else {
// prefer the composite rate if it is present
spread = parseRate(compositeSpreadText);
}
List<Point> points = curveData.get(id);
if (points == null) {
points = Lists.newArrayList();
curveData.put(id, points);
}
points.add(new Point(term, maturity, spread));
}
for (IsdaIndexCreditCurveInputsId curveId : curveData.keySet()) {
MarkitRedCode redCode = MarkitRedCode.from(curveId.getReferenceInformation().getIndexId());
StaticData staticData = staticDataMap.get(redCode);
ArgChecker.notNull(staticData, "Did not find a static data record for " + redCode);
CdsConvention convention = staticData.getConvention();
double recoveryRate = staticData.getRecoveryRate();
double indexFactor = staticData.getIndexFactor();
// TODO add fromDate handling
String creditCurveName = curveId.toString();
List<Point> points = curveData.get(curveId);
Period[] periods = points.stream().map(s -> s.getTenor().getPeriod()).toArray(Period[]::new);
LocalDate[] endDates = points.stream().map(s -> s.getDate()).toArray(LocalDate[]::new);
double[] rates = points.stream().mapToDouble(s -> s.getRate()).toArray();
IsdaCreditCurveInputs curveInputs = IsdaCreditCurveInputs.of(
CurveName.of(creditCurveName),
periods,
endDates,
rates,
convention,
indexFactor);
builder.addValue(curveId, curveInputs);
IsdaIndexRecoveryRateId recoveryRateId = IsdaIndexRecoveryRateId.of(curveId.getReferenceInformation());
CdsRecoveryRate cdsRecoveryRate = CdsRecoveryRate.of(recoveryRate);
builder.addValue(recoveryRateId, cdsRecoveryRate);
}
}
// parses the static data file
private static Map<MarkitRedCode, StaticData> parseStaticData(CharSource source) {
CsvFile csv = CsvFile.of(source, true);
Map<MarkitRedCode, StaticData> result = Maps.newHashMap();
for (CsvRow row : csv.rows()) {
String redCodeText = row.getField("RedCode");
String fromDateText = row.getField("From Date");
String conventionText = row.getField("Convention");
String recoveryRateText = row.getField("Recovery Rate");
String indexFactorText = row.getField("Index Factor");
MarkitRedCode redCode = MarkitRedCode.of(redCodeText);
LocalDate fromDate = LocalDate.parse(fromDateText, DATE_FORMAT);
CdsConvention convention = CdsConvention.of(conventionText);
double recoveryRate = parseRate(recoveryRateText);
double indexFactor = Double.parseDouble(indexFactorText);
result.put(redCode, new StaticData(fromDate, convention, recoveryRate, indexFactor));
}
return result;
}
//-------------------------------------------------------------------------
/**
* Stores the parsed static data.
*/
private static final class StaticData {
private LocalDate fromDate;
private CdsConvention convention;
private double recoveryRate;
private double indexFactor;
private StaticData(LocalDate fromDate, CdsConvention convention, double recoveryRate, double indexFactor) {
this.fromDate = fromDate;
this.convention = convention;
this.recoveryRate = recoveryRate;
this.indexFactor = indexFactor;
}
@SuppressWarnings("unused")
public LocalDate getFromDate() {
return fromDate;
}
public CdsConvention getConvention() {
return convention;
}
public double getRecoveryRate() {
return recoveryRate;
}
public double getIndexFactor() {
return indexFactor;
}
}
//-------------------------------------------------------------------------
/**
* Stores the parsed data points.
*/
private static final class Point {
private final Tenor tenor;
private final LocalDate date;
private final double rate;
private Point(Tenor tenor, LocalDate date, double rate) {
this.tenor = tenor;
this.date = date;
this.rate = rate;
}
public Tenor getTenor() {
return tenor;
}
public LocalDate getDate() {
return date;
}
public double getRate() {
return rate;
}
}
// Converts from a string percentage rate with a percent sign to a double rate
// e.g. 0.12% => 0.0012d
private static double parseRate(String input) {
return Double.parseDouble(input.replace("%", "")) / 100d;
}
}