/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.test;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeParseException;
import au.com.bytecode.opencsv.CSVParser;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.financial.convention.FloatingIndex;
import com.opengamma.financial.convention.businessday.BusinessDayConvention;
import com.opengamma.financial.convention.businessday.BusinessDayConventionFactory;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.financial.convention.daycount.DayCountFactory;
import com.opengamma.financial.convention.frequency.Frequency;
import com.opengamma.financial.convention.frequency.SimpleFrequencyFactory;
import com.opengamma.financial.security.swap.FixedInterestRateLeg;
import com.opengamma.financial.security.swap.FloatingInterestRateLeg;
import com.opengamma.financial.security.swap.FloatingRateType;
import com.opengamma.financial.security.swap.InterestRateNotional;
import com.opengamma.financial.security.swap.Notional;
import com.opengamma.financial.security.swap.SwapLeg;
import com.opengamma.financial.security.swap.SwapSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.GUIDGenerator;
import com.opengamma.util.ResourceUtils;
import com.opengamma.util.csv.CSVDocumentReader;
import com.opengamma.util.money.Currency;
/**
*
*/
public class IRSwapTradeParser {
private static final Logger s_logger = LoggerFactory.getLogger(IRSwapTradeParser.class);
private static final String EFFECTIVE_DATE = "Effective Date";
private static final String MATURITY_DATE = "Maturity Date";
private static final String DIRECTION = "Direction";
private static final String PAY_LEG_TYPE = "LEG1_TYPE";
private static final String PAY_LEG_BUS_DAY_CONV = "LEG1_PAY_ADJ_BUS_DAY_CONV";
private static final String PAY_LEG_DAYCOUNT = "LEG1_DAYCOUNT";
private static final String PAY_LEG_FREQUENCY = "LEG1_PAY_FREQ";
private static final String PAY_LEG_REGION = "LEG1_PAY_ADJ_CAL";
private static final String PAY_NOTIONAL = "LEG1_NOTIONAL";
private static final String PAY_CURRENCY = "LEG1_CCY";
private static final String PAY_LEG_EOM = "LEG1_ROLL_CONV";
private static final String PAY_LEG_FIXED_RATE = "LEG1_FIXED_RATE";
private static final String PAY_LEG_INDEX = "LEG1_INDEX";
private static final String RECIEVE_LEG_TYPE = "LEG2_TYPE";
private static final String RECIEVE_LEG_BUS_DAY_CONV = "LEG2_PAY_ADJ_BUS_DAY_CONV";
private static final String RECIEVE_LEG_DAYCOUNT = "LEG2_DAYCOUNT";
private static final String RECIEVE_LEG_FREQUENCY = "LEG2_PAY_FREQ";
private static final String RECIEVE_LEG_REGION = "LEG2_PAY_ADJ_CAL";
private static final String RECIEVE_NOTIONAL = "LEG2_NOTIONAL";
private static final String RECIEVE_CURRENCY = "LEG2_CCY";
private static final String RECIEVE_LEG_EOM = "LEG2_ROLL_CONV";
private static final String RECIEVE_LEG_FIXED_RATE = "LEG2_FIXED_RATE";
private static final String RECIEVE_LEG_INDEX = "LEG2_INDEX";
private static final String CLEARED_TRADE_ID = "Cleared Trade ID";
private static final String STATUS = "Status";
private static final String ERS_PV = "ERS PV";
private static final String PRODUCT_TYPE = "PRODUCT_TYPE";
private static final String CME_SWAP_INDICATOR = "CME Swap Indicator";
private static final String CLIENT_ID = "Client ID";
private static final DateTimeFormatter s_dateFormatter = DateTimeFormatter.ofPattern("d/M/yyyy");
private static final Properties s_dayCountMapping = getDayCountMapping();
private static final Properties s_businessDayConventionMapping = getBusinessDayConventionMapping();
private static final Map<String, com.opengamma.financial.convention.FloatingIndex> s_floatingIndexMapping = getFloatingIndexMapping();
private static final List<String> s_unSupportedProductTypes = Lists.newArrayList("FRA", "ZCS");
private static final List<String> s_stubTrades = Lists.newArrayList("shortfront", "longfront", "shortback", "longback");
private static final List<String> s_compoundTrades = Lists.newArrayList("cmpd", "straightcmpnd", "flatcmpnd");
private final Random _random = new Random();
public List<IRSwapSecurity> parseCSVFile(URL tradeFileUrl) {
ArgumentChecker.notNull(tradeFileUrl, "tradeFileUrl");
List<IRSwapSecurity> trades = Lists.newArrayList();
CSVDocumentReader csvDocumentReader = new CSVDocumentReader(tradeFileUrl, CSVParser.DEFAULT_SEPARATOR,
CSVParser.DEFAULT_QUOTE_CHARACTER, CSVParser.DEFAULT_ESCAPE_CHARACTER, new FudgeContext());
List<FudgeMsg> rowsWithError = Lists.newArrayList();
List<FudgeMsg> unsupportedProdTypes = Lists.newArrayList();
List<FudgeMsg> stubTrades = Lists.newArrayList();
List<FudgeMsg> compoundTrades = Lists.newArrayList();
List<FudgeMsg> missingPV = Lists.newArrayList();
List<FudgeMsg> terminatedTrades = Lists.newArrayList();
int count = 1;
for (FudgeMsg row : csvDocumentReader) {
count++;
SwapSecurity swapSecurity = null;
try {
if (isUnsupportedProductType(row)) {
unsupportedProdTypes.add(row);
continue;
}
if (isStubTrade(row)) {
stubTrades.add(row);
continue;
}
if (isCompoundTrade(row)) {
compoundTrades.add(row);
continue;
}
if (isErsPVMissing(row)) {
missingPV.add(row);
continue;
}
if (isTeminatedTrade(row)) {
terminatedTrades.add(row);
continue;
}
swapSecurity = createSwapSecurity(row);
trades.add(IRSwapSecurity.of(swapSecurity, row));
} catch (Exception ex) {
ex.printStackTrace();
rowsWithError.add(row);
}
}
logErrors("unsupportedProdTypes", unsupportedProdTypes, count);
logErrors("stubTrades", stubTrades, count);
logErrors("compoundTrades", compoundTrades, count);
logErrors("missingPV", missingPV, count);
logErrors("terminatedTrades", terminatedTrades, count);
s_logger.warn("Total unprocessed rows: {} out of {}", rowsWithError.size(), count);
for (FudgeMsg fudgeMsg : rowsWithError) {
s_logger.warn("{}", fudgeMsg);
}
return trades;
}
private void logErrors(String type, List<FudgeMsg> unsupportedProdTypes, int totalRows) {
s_logger.warn("Total {} rows: {} out of {}", type, unsupportedProdTypes.size(), totalRows);
for (FudgeMsg fudgeMsg : unsupportedProdTypes) {
s_logger.warn("{}", fudgeMsg);
}
}
private boolean isTeminatedTrade(FudgeMsg row) {
String status = row.getString(STATUS);
return "TERMINATED".equalsIgnoreCase(status);
}
private boolean isErsPVMissing(FudgeMsg row) {
return row.getString(ERS_PV) == null;
}
private boolean isCompoundTrade(FudgeMsg row) {
String clientId = row.getString(CLIENT_ID);
if (clientId != null) {
for (String cmpKeyWord : s_compoundTrades) {
if (clientId.toLowerCase().contains(cmpKeyWord)) {
return true;
}
}
}
return false;
}
private boolean isStubTrade(FudgeMsg row) {
String clientId = row.getString(CLIENT_ID);
if (clientId != null) {
for (String stubKeyWord : s_stubTrades) {
if (clientId.toLowerCase().contains(stubKeyWord)) {
return true;
}
}
}
return false;
}
private boolean isUnsupportedProductType(FudgeMsg row) {
String productType = row.getString(PRODUCT_TYPE);
if (productType == null) {
return true;
}
return s_unSupportedProductTypes.contains(productType.toUpperCase());
}
private static Map<String, FloatingIndex> getFloatingIndexMapping() {
Map<String, FloatingIndex> result = Maps.newHashMap();
for (FloatingIndex floatingIndex : FloatingIndex.values()) {
result.put(floatingIndex.getIsdaName().toUpperCase(), floatingIndex);
}
return result;
}
private static Properties getBusinessDayConventionMapping() {
return loadPropertiesFromClassPath("classpath:com/opengamma/financial/analytics/test/businessDayConventionMapping.properties");
}
private static Properties getDayCountMapping() {
return loadPropertiesFromClassPath("classpath:com/opengamma/financial/analytics/test/dayCountMapping.properties");
}
private static Properties loadPropertiesFromClassPath(String resourceLocation) {
Resource resource = ResourceUtils.createResource(resourceLocation);
Properties properties = new Properties();
try (InputStream stream = resource.getInputStream()) {
properties.load(stream);
} catch (IOException ex) {
throw new OpenGammaRuntimeException("Error loading " + resourceLocation + " from classpath", ex);
}
return properties;
}
private SwapSecurity createSwapSecurity(FudgeMsg row) {
LocalDate effectiveDate = parseDate(row, EFFECTIVE_DATE);
LocalDate maturityDate = parseDate(row, MATURITY_DATE);
SwapLeg payLeg = parsePayLeg(row);
SwapLeg receiveLeg = parseReceiveLeg(row);
String direction = row.getString(DIRECTION);
if (direction != null) {
if (direction.equalsIgnoreCase("R")) {
SwapLeg temp = payLeg;
payLeg = receiveLeg;
receiveLeg = temp;
}
}
SwapSecurity swap = new SwapSecurity(effectiveDate.atStartOfDay(ZoneOffset.UTC),
effectiveDate.atStartOfDay(ZoneOffset.UTC),
maturityDate.atStartOfDay(ZoneOffset.UTC),
"Cpty " + _random.nextInt(100), payLeg, receiveLeg);
swap.addExternalId(ExternalId.of("UUID", GUIDGenerator.generate().toString()));
swap.setName(getSwapName(row, swap));
return swap;
}
private String getSwapName(FudgeMsg row, SwapSecurity swap) {
FixedInterestRateLeg fixedLeg = null;
FloatingInterestRateLeg floatingLeg = null;
if (swap.getPayLeg() instanceof FixedInterestRateLeg) {
fixedLeg = (FixedInterestRateLeg) swap.getPayLeg();
floatingLeg = (FloatingInterestRateLeg) swap.getReceiveLeg();
} else {
fixedLeg = (FixedInterestRateLeg) swap.getReceiveLeg();
floatingLeg = (FloatingInterestRateLeg) swap.getPayLeg();
}
InterestRateNotional notional = (InterestRateNotional) fixedLeg.getNotional();
return String.format("#%s %s : pay %s%% fixed vs %s, start=%s, maturity=%s, notional=%s %s", row.getString(CLEARED_TRADE_ID),
row.getString(CME_SWAP_INDICATOR),
(fixedLeg.getRate() * 100.0),
floatingLeg.getFloatingReferenceRateId().getValue(),
swap.getEffectiveDate().toLocalDate(),
swap.getMaturityDate().toLocalDate(),
notional.getCurrency(),
notional.getAmount());
}
private SwapLeg parseReceiveLeg(FudgeMsg row) {
SwapLeg swapLeg = null;
String legType = row.getString(RECIEVE_LEG_TYPE);
if (legType != null) {
DayCount payDayCount = parseDayCount(row, RECIEVE_LEG_DAYCOUNT);
Frequency payFrequency = parseFrequency(row, RECIEVE_LEG_FREQUENCY);
ExternalId payRegionId = parseRegionId(row, RECIEVE_LEG_REGION);
BusinessDayConvention payDayConvention = parseBusinessDayConvention(row, RECIEVE_LEG_BUS_DAY_CONV);
Boolean payEom = parseEOM(row, RECIEVE_LEG_EOM);
Notional payNotional = parseNotional(row, RECIEVE_NOTIONAL, RECIEVE_CURRENCY);
switch (legType) {
case "FIXED":
Double payFixedRate = parseFixedRate(row, RECIEVE_LEG_FIXED_RATE);
try {
swapLeg = new FixedInterestRateLeg(payDayCount, payFrequency, payRegionId,
payDayConvention, payNotional, payEom, payFixedRate);
} catch (Exception ex) {
s_logger.warn(String.format("Error creating swap security from %s", row), ex);
}
break;
case "FLOAT":
ExternalId payFloatingRateId = parseFloatingRateId(row, RECIEVE_LEG_INDEX, payFrequency);
try {
swapLeg = new FloatingInterestRateLeg(payDayCount, payFrequency, payRegionId, payDayConvention,
payNotional, payEom, payFloatingRateId, FloatingRateType.IBOR);
} catch (Exception ex) {
s_logger.warn(String.format("Error creating swap security from %s", row), ex);
}
break;
default:
s_logger.warn("Unsupported leg type: {} in {}", legType, row);
break;
}
} else {
s_logger.warn("Missing leg type value in column:{} in row:{}", RECIEVE_LEG_TYPE, row);
}
return swapLeg;
}
private SwapLeg parsePayLeg(FudgeMsg row) {
SwapLeg swapLeg = null;
String legType = row.getString(PAY_LEG_TYPE);
if (legType != null) {
DayCount payDayCount = parseDayCount(row, PAY_LEG_DAYCOUNT);
Frequency payFrequency = parseFrequency(row, PAY_LEG_FREQUENCY);
ExternalId payRegionId = parseRegionId(row, PAY_LEG_REGION);
BusinessDayConvention payDayConvention = parseBusinessDayConvention(row, PAY_LEG_BUS_DAY_CONV);
Boolean payEom = parseEOM(row, PAY_LEG_EOM);
Notional payNotional = parseNotional(row, PAY_NOTIONAL, PAY_CURRENCY);
switch (legType) {
case "FIXED":
Double payFixedRate = parseFixedRate(row, PAY_LEG_FIXED_RATE);
try {
swapLeg = new FixedInterestRateLeg(payDayCount, payFrequency, payRegionId,
payDayConvention, payNotional, payEom, payFixedRate);
} catch (Exception ex) {
s_logger.warn(String.format("Error creating swap security from %s", row), ex);
}
break;
case "FLOAT":
ExternalId payFloatingRateId = parseFloatingRateId(row, PAY_LEG_INDEX, payFrequency);
try {
swapLeg = new FloatingInterestRateLeg(payDayCount, payFrequency, payRegionId, payDayConvention,
payNotional, payEom, payFloatingRateId, FloatingRateType.IBOR);
} catch (Exception ex) {
s_logger.warn(String.format("Error creating swap security from %s", row), ex);
}
break;
default:
s_logger.warn("Unsupported leg type:{} in {}", legType, row);
break;
}
} else {
s_logger.warn("Missing leg type value in column:{} in row:{}", PAY_LEG_TYPE, row);
}
return swapLeg;
}
private ExternalId parseFloatingRateId(FudgeMsg row, String columnName, Frequency frequency) {
ExternalId floatingRateId = null;
if (frequency != null) {
String floatingIndexStr = row.getString(columnName);
if (floatingIndexStr != null) {
FloatingIndex floatingIndex = s_floatingIndexMapping.get(floatingIndexStr.toUpperCase());
if (floatingIndex != null) {
floatingRateId = floatingIndex.toFrequencySpecificExternalId(frequency);
} else {
s_logger.warn("Unsupported floating Index: {} in row:{}", floatingIndexStr, row);
}
}
}
return floatingRateId;
}
private Double parseFixedRate(FudgeMsg row, String columnName) {
Double rate = null;
String rateStr = row.getString(columnName);
try {
rate = Double.parseDouble(rateStr);
} catch (Exception ex) {
s_logger.warn("Missing or invalid value for fixed rate in column:{} in row:{}", columnName, row);
}
return rate;
}
private Boolean parseEOM(FudgeMsg row, String columnName) {
Boolean isEom = null;
String rollConvStr = row.getString(columnName);
if (rollConvStr != null) {
isEom = rollConvStr.equals("EOM");
} else {
s_logger.warn("Missing roll convention in column:{} in row:{}", columnName, row);
}
return isEom;
}
private Notional parseNotional(FudgeMsg row, String notionalColumn, String ccyColumn) {
Notional notional = null;
String ccyStr = row.getString(ccyColumn);
if (ccyStr != null) {
Currency currency = Currency.of(ccyStr);
String notionalAmount = row.getString(notionalColumn);
notionalAmount = notionalAmount.replace(",", "");
if (notionalAmount != null) {
notional = new InterestRateNotional(currency, Double.valueOf(notionalAmount));
} else {
s_logger.warn("Missing notional value in column:{} in row:{}", notionalColumn, row);
}
} else {
s_logger.warn("Missing currency in column:{} in row:{}", ccyColumn, row);
}
return notional;
}
private ExternalId parseRegionId(FudgeMsg row, String columnName) {
ExternalId regionId = null;
String regionIdStr = row.getString(columnName);
if (regionIdStr != null) {
String[] ids = regionIdStr.split(",");
regionId = ExternalSchemes.isdaHoliday(ids[0]);
} else {
s_logger.warn("Missing RegionId in column:{} in row:{}", columnName, row);
}
return regionId;
}
private Frequency parseFrequency(FudgeMsg row, String columnName) {
Frequency frequency = null;
String frequencyStr = row.getString(columnName);
if (frequencyStr != null) {
try {
frequency = SimpleFrequencyFactory.of(frequencyStr.toLowerCase());
} catch (IllegalArgumentException ex) {
s_logger.warn("Unknown freqency: {} in column: {} in row: {}", frequencyStr, columnName, row);
}
} else {
s_logger.warn("Missing frequency in column:{} in row:{}", columnName, row);
}
return frequency;
}
private DayCount parseDayCount(FudgeMsg row, String columnName) {
DayCount dayCount = null;
String dayCountStr = row.getString(columnName);
if (dayCountStr != null) {
String ogDayCount = s_dayCountMapping.getProperty(dayCountStr, null);
if (ogDayCount != null) {
dayCount = DayCountFactory.of(ogDayCount);
} else {
s_logger.warn("Missing dayCount mapping for {} in column:{} in row:{}", dayCountStr, columnName, row);
}
} else {
s_logger.warn("Missing day count in column:{} in row:{}", columnName, row);
}
return dayCount;
}
private BusinessDayConvention parseBusinessDayConvention(FudgeMsg row, String columnName) {
BusinessDayConvention convention = null;
String conventionStr = row.getString(columnName);
if (conventionStr != null) {
String ogBusDayConvention = s_businessDayConventionMapping.getProperty(conventionStr, null);
if (ogBusDayConvention != null) {
convention = BusinessDayConventionFactory.of(ogBusDayConvention);
} else {
s_logger.warn("Missing convention mapping for {}", conventionStr);
}
} else {
s_logger.warn("Missing business day convention in column:{}", columnName);
}
return convention;
}
private LocalDate parseDate(FudgeMsg row, String columnName) {
LocalDate result = null;
String dateStr = row.getString(columnName);
if (dateStr != null) {
try {
result = LocalDate.parse(dateStr, s_dateFormatter);
} catch (DateTimeParseException ex) {
s_logger.error("Invalid dateValue:{} in column:{}, skipping...", dateStr, columnName);
}
}
return result;
}
}