/** * 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.io.IOException; import java.io.UncheckedIOException; 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 java.util.Scanner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.CharSource; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.date.Tenor; 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.IsdaSingleNameCreditCurveInputsId; import com.opengamma.strata.pricer.credit.IsdaSingleNameRecoveryRateId; import com.opengamma.strata.product.credit.RestructuringClause; import com.opengamma.strata.product.credit.SeniorityLevel; import com.opengamma.strata.product.credit.SingleNameReferenceInformation; import com.opengamma.strata.product.credit.type.CdsConvention; /** * Parser to load daily credit curve information provided by Markit. * <p> * The columns are defined as {@code * Date Ticker ShortName MarkitRedCode Tier * Ccy DocClause Contributor Spread6m Spread1y * Spread2y Spread3y Spread4y Spread5y Spread7y * Spread10y Spread15y Spread20y Spread30y Recovery * Rating6m Rating1y Rating2y Rating3y Rating4y * Rating5y Rating7y Rating10y Rating15y Rating20y * Rating30y CompositeCurveRating CompositeDepth5y Sector Region * Country AvRating ImpliedRating CompositeLevel6m CompositeLevel1y * CompositeLevel2y CompositeLevel3y CompositeLevel4y CompositeLevel5y CompositeLevel7y * CompositeLevel10y CompositeLevel15y CompositeLevel20y CompositeLevel30y CompositeLevelRecovery}. * <p> * Also reads static data from a csv. * <p> * RedCode,Convention */ public class MarkitSingleNameCreditCurveDataParser { // 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); // Index used to access the specified columns of string data in the file private static final int DATE = 0; private static final int RED_CODE = 3; private static final int TIER = 4; private static final int CURRENCY = 5; private static final int DOCS_CLAUSE = 6; private static final int FIRST_SPREAD_COLUMN = 8; private static final int RECOVERY = 19; private static final List<Tenor> TENORS = ImmutableList.of( Tenor.TENOR_6M, Tenor.TENOR_1Y, Tenor.TENOR_2Y, Tenor.TENOR_3Y, Tenor.TENOR_4Y, Tenor.TENOR_5Y, Tenor.TENOR_7Y, Tenor.TENOR_10Y, Tenor.TENOR_15Y, Tenor.TENOR_20Y, Tenor.TENOR_30Y); /** * 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<MarkitRedCode, CdsConvention> conventions = parseStaticData(staticDataSource); try (Scanner scanner = new Scanner(curveSource.openStream())) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); // skip over header rows if (line.startsWith("V5 CDS Composites by Convention") || line.trim().isEmpty() || line.startsWith("\"Date\",")) { continue; } String[] columns = line.split(","); for (int i = 0; i < columns.length; i++) { // get rid of quotes and trim the string columns[i] = columns[i].replaceFirst("^\"", "").replaceFirst("\"$", "").trim(); } LocalDate valuationDate = LocalDate.parse(columns[DATE], DATE_FORMAT); MarkitRedCode redCode = MarkitRedCode.of(columns[RED_CODE]); SeniorityLevel seniorityLevel = MarkitSeniorityLevel.valueOf(columns[TIER]).translate(); Currency currency = Currency.parse(columns[CURRENCY]); RestructuringClause restructuringClause = MarkitRestructuringClause.valueOf(columns[DOCS_CLAUSE]).translate(); double recoveryRate = parseRate(columns[RECOVERY]); SingleNameReferenceInformation referenceInformation = SingleNameReferenceInformation.of( redCode.toStandardId(), seniorityLevel, currency, restructuringClause); IsdaSingleNameCreditCurveInputsId curveId = IsdaSingleNameCreditCurveInputsId.of(referenceInformation); List<Period> periodsList = Lists.newArrayList(); List<Double> ratesList = Lists.newArrayList(); for (int i = 0; i < TENORS.size(); i++) { String rateString = columns[FIRST_SPREAD_COLUMN + i]; if (rateString.isEmpty()) { // no data at this point continue; } periodsList.add(TENORS.get(i).getPeriod()); ratesList.add(parseRate(rateString)); } String creditCurveName = curveId.toString(); CdsConvention cdsConvention = conventions.get(redCode); Period[] periods = periodsList.stream().toArray(Period[]::new); LocalDate[] endDates = Lists .newArrayList(periods) .stream() .map(p -> cdsConvention.calculateUnadjustedMaturityDateFromValuationDate(valuationDate, p)) .toArray(LocalDate[]::new); double[] rates = ratesList.stream().mapToDouble(s -> s).toArray(); double unitScalingFactor = 1d; // for single name, we don't do any scaling (no index factor) IsdaCreditCurveInputs curveInputs = IsdaCreditCurveInputs.of( CurveName.of(creditCurveName), periods, endDates, rates, cdsConvention, unitScalingFactor); builder.addValue(curveId, curveInputs); IsdaSingleNameRecoveryRateId recoveryRateId = IsdaSingleNameRecoveryRateId.of(referenceInformation); CdsRecoveryRate cdsRecoveryRate = CdsRecoveryRate.of(recoveryRate); builder.addValue(recoveryRateId, cdsRecoveryRate); } } catch (IOException ex) { throw new UncheckedIOException(ex); } } // parses the static data file of RED code to convention private static Map<MarkitRedCode, CdsConvention> parseStaticData(CharSource source) { CsvFile csv = CsvFile.of(source, true); Map<MarkitRedCode, CdsConvention> result = Maps.newHashMap(); for (CsvRow row : csv.rows()) { String redCodeText = row.getField("RedCode"); String conventionText = row.getField("Convention"); result.put(MarkitRedCode.of(redCodeText), CdsConvention.of(conventionText)); } return result; } // 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; } }