/** * 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; } }