Skip to content

Commit

Permalink
Add support for CDS in CSV (#1956)
Browse files Browse the repository at this point in the history
* Add support for CDS in CSV

Follow standard pattern of other asset classes
  • Loading branch information
jodastephen committed Apr 25, 2019
1 parent 6e7a348 commit 19ee4f5
Show file tree
Hide file tree
Showing 10 changed files with 648 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package com.opengamma.strata.loader.csv;

import static com.opengamma.strata.basics.date.BusinessDayConventions.FOLLOWING;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.CURRENCY_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.DIRECTION_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.NOTIONAL_FIELD;
Expand Down Expand Up @@ -63,7 +64,7 @@ private static BulletPaymentTrade parseRow(CsvRow row, TradeInfo info, TradeCsvI
CurrencyAmount amount = CsvLoaderUtils.parseCurrencyAmountWithDirection(
row, CURRENCY_FIELD, NOTIONAL_FIELD, DIRECTION_FIELD);
AdjustableDate date = CsvLoaderUtils.parseAdjustableDate(
row, PAYMENT_DATE_FIELD, PAYMENT_DATE_CNV_FIELD, PAYMENT_DATE_CAL_FIELD, amount.getCurrency());
row, PAYMENT_DATE_FIELD, PAYMENT_DATE_CNV_FIELD, PAYMENT_DATE_CAL_FIELD, FOLLOWING, amount.getCurrency());

BulletPayment payment = BulletPayment.builder()
.payReceive(PayReceive.ofSignedAmount(amount.getAmount()))
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/
package com.opengamma.strata.loader.csv;

import static com.opengamma.strata.basics.date.BusinessDayConventions.FOLLOWING;
import static com.opengamma.strata.collect.Guavate.toImmutableMap;

import java.math.BigDecimal;
Expand All @@ -22,7 +21,9 @@
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.BusinessDayConvention;
import com.opengamma.strata.basics.date.BusinessDayConventions;
import com.opengamma.strata.basics.date.DaysAdjustment;
import com.opengamma.strata.basics.date.HolidayCalendarId;
import com.opengamma.strata.basics.date.HolidayCalendarIds;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.io.CsvRow;
Expand Down Expand Up @@ -275,13 +276,12 @@ static AdjustableDate parseAdjustableDate(

/**
* Parses a business day adjustment, defaulting the adjustment using the currency.
* <p>
* The default uses {@link BusinessDayConventions#FOLLOWING}.
*
* @param row the CSV row to parse
* @param dateField the date field
* @param conventionField the convention field
* @param calendarField the calendar field
* @param defaultConvention the default convention
* @param currency the applicable currency, used for defaulting
* @return the adjustment
* @throws IllegalArgumentException if the row cannot be parsed
Expand All @@ -291,11 +291,12 @@ static AdjustableDate parseAdjustableDate(
String dateField,
String conventionField,
String calendarField,
BusinessDayConvention defaultConvention,
Currency currency) {

LocalDate date = LoaderUtils.parseDate(row.getValue(dateField));
BusinessDayAdjustment adj = parseBusinessDayAdjustment(row, conventionField, calendarField)
.orElseGet(() -> BusinessDayAdjustment.of(FOLLOWING, HolidayCalendarId.defaultByCurrency(currency)));
.orElseGet(() -> BusinessDayAdjustment.of(defaultConvention, HolidayCalendarId.defaultByCurrency(currency)));
return AdjustableDate.of(date, adj);
}

Expand Down Expand Up @@ -329,6 +330,37 @@ static Optional<BusinessDayAdjustment> parseBusinessDayAdjustment(
return Optional.empty();
}

/**
* Parses days adjustment from CSV.
*
* @param row the CSV row to parse
* @param daysField the days field
* @param daysCalField the days calendar field
* @param cnvField the convention field
* @param calField the calendar field
* @return the adjustment
* @throws IllegalArgumentException if the row cannot be parsed
*/
static DaysAdjustment parseDaysAdjustment(
CsvRow row,
String daysField,
String daysCalField,
String cnvField,
String calField) {

int days = LoaderUtils.parseInteger(row.getValue(daysField));
HolidayCalendarId daysCal = row.findValue(daysCalField)
.map(s -> HolidayCalendarId.of(s))
.orElse(HolidayCalendarIds.NO_HOLIDAYS);
BusinessDayAdjustment bda = parseBusinessDayAdjustment(row, cnvField, calField)
.orElse(BusinessDayAdjustment.NONE);
return DaysAdjustment.builder()
.days(days)
.calendar(daysCal)
.adjustment(bda)
.build();
}

//-------------------------------------------------------------------------
/**
* Parses a currency amount.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@
import static com.opengamma.strata.loader.csv.TradeCsvLoader.DATE_ADJ_CNV_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.DAY_COUNT_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.DIRECTION_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.END_DATE_CAL_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.END_DATE_CNV_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.END_DATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.FIRST_REGULAR_START_DATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.FIXED_RATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.FREQUENCY_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.INDEX_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.LAST_REGULAR_END_DATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.NOTIONAL_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.OVERRIDE_START_DATE_CAL_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.OVERRIDE_START_DATE_CNV_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.OVERRIDE_START_DATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.ROLL_CONVENTION_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.START_DATE_CAL_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.START_DATE_CNV_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.START_DATE_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.STUB_CONVENTION_FIELD;
import static com.opengamma.strata.loader.csv.TradeCsvLoader.TYPE_FIELD;

import java.time.LocalDate;
Expand Down Expand Up @@ -104,19 +116,6 @@ final class FullSwapTradeCsvPlugin implements TradeTypeCsvWriter<SwapTrade> {
public static final FullSwapTradeCsvPlugin INSTANCE = new FullSwapTradeCsvPlugin();

// CSV column headers
private static final String FREQUENCY_FIELD = "Frequency";
private static final String START_DATE_CNV_FIELD = "Start Date Convention";
private static final String START_DATE_CAL_FIELD = "Start Date Calendar";
private static final String END_DATE_CNV_FIELD = "End Date Convention";
private static final String END_DATE_CAL_FIELD = "End Date Calendar";
private static final String ROLL_CONVENTION_FIELD = "Roll Convention";
private static final String STUB_CONVENTION_FIELD = "Stub Convention";
private static final String FIRST_REGULAR_START_DATE_FIELD = "First Regular Start Date";
private static final String LAST_REGULAR_END_DATE_FIELD = "Last Regular End Date";
private static final String OVERRIDE_START_DATE_FIELD = "Override Start Date";
private static final String OVERRIDE_START_DATE_CNV_FIELD = "Override Start Date Convention";
private static final String OVERRIDE_START_DATE_CAL_FIELD = "Override Start Date Calendar";

private static final String PAYMENT_FREQUENCY_FIELD = "Payment Frequency";
private static final String PAYMENT_RELATIVE_TO_FIELD = "Payment Relative To";
private static final String PAYMENT_OFFSET_DAYS_FIELD = "Payment Offset Days";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.opengamma.strata.product.SecurityTrade;
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeInfoBuilder;
import com.opengamma.strata.product.credit.CdsTrade;
import com.opengamma.strata.product.deposit.TermDepositTrade;
import com.opengamma.strata.product.fra.FraTrade;
import com.opengamma.strata.product.fx.FxSingleTrade;
Expand Down Expand Up @@ -237,4 +238,21 @@ public default FxVanillaOptionTrade completeTrade(CsvRow row, FxVanillaOptionTra
return completeTradeCommon(row, trade);
}

/**
* Completes the CDS trade, potentially parsing additional columns.
* <p>
* This is called after the trade has been parsed and after
* {@link #parseTradeInfo(CsvRow, TradeInfoBuilder)}.
* <p>
* By default this calls {@link #completeTradeCommon(CsvRow, Trade)}.
*
* @param row the CSV row to parse
* @param trade the parsed trade
* @return the updated trade
*/
public default CdsTrade completeTrade(CsvRow row, CdsTrade trade) {
//do nothing
return completeTradeCommon(row, trade);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeInfo;
import com.opengamma.strata.product.TradeInfoBuilder;
import com.opengamma.strata.product.credit.CdsTrade;
import com.opengamma.strata.product.deposit.TermDepositTrade;
import com.opengamma.strata.product.deposit.type.TermDepositConventions;
import com.opengamma.strata.product.fra.FraTrade;
Expand Down Expand Up @@ -238,6 +239,19 @@ public final class TradeCsvLoader {
static final String PREMIUM_DATE_CAL_FIELD = "Premium Date Calendar";
static final String FRA_DISCOUNTING_FIELD = "FRA Discounting Method";

static final String FREQUENCY_FIELD = "Frequency";
static final String START_DATE_CNV_FIELD = "Start Date Convention";
static final String START_DATE_CAL_FIELD = "Start Date Calendar";
static final String END_DATE_CNV_FIELD = "End Date Convention";
static final String END_DATE_CAL_FIELD = "End Date Calendar";
static final String ROLL_CONVENTION_FIELD = "Roll Convention";
static final String STUB_CONVENTION_FIELD = "Stub Convention";
static final String FIRST_REGULAR_START_DATE_FIELD = "First Regular Start Date";
static final String LAST_REGULAR_END_DATE_FIELD = "Last Regular End Date";
static final String OVERRIDE_START_DATE_FIELD = "Override Start Date";
static final String OVERRIDE_START_DATE_CNV_FIELD = "Override Start Date Convention";
static final String OVERRIDE_START_DATE_CAL_FIELD = "Override Start Date Calendar";

// basic CSV column headers
static final String TYPE_FIELD = "Strata Trade Type";
static final String ID_SCHEME_FIELD = "Id Scheme";
Expand Down Expand Up @@ -511,6 +525,11 @@ private <T extends Trade> ValueWithFailures<List<T>> parseFile(CsvIterator csv,
trades.add(tradeType.cast(FxVanillaOptionTradeCsvPlugin.parse(row, info, resolver)));
}
break;
case "CDS":
if (tradeType == CdsTrade.class || tradeType == Trade.class) {
trades.add(tradeType.cast(CdsTradeCsvPlugin.parse(row, info, resolver)));
}
break;
default:
failures.add(FailureItem.of(
FailureReason.PARSING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import com.opengamma.strata.product.bond.BondFutureOptionTrade;
import com.opengamma.strata.product.bond.CapitalIndexedBondTrade;
import com.opengamma.strata.product.bond.FixedCouponBondTrade;
import com.opengamma.strata.product.credit.CdsTrade;
import com.opengamma.strata.product.deposit.TermDepositTrade;
import com.opengamma.strata.product.dsf.DsfTrade;
import com.opengamma.strata.product.etd.EtdFutureTrade;
Expand Down Expand Up @@ -99,6 +100,7 @@ public final class TradeCsvWriter {
.put(FxSwapTrade.class, FxSwapTradeCsvPlugin.INSTANCE)
.put(SwaptionTrade.class, SwaptionTradeCsvPlugin.INSTANCE) // then options
.put(FxVanillaOptionTrade.class, FxVanillaOptionTradeCsvPlugin.INSTANCE)
.put(CdsTrade.class, CdsTradeCsvPlugin.INSTANCE) // then credit
.put(SecurityTrade.class, SecurityTradeCsvPlugin.INSTANCE) // then securities
.put(EtdFutureTrade.class, SecurityTradeCsvPlugin.INSTANCE)
.put(EtdOptionTrade.class, SecurityTradeCsvPlugin.INSTANCE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void test_parseAdjustableDate_defaulting() {
ImmutableList<String> firstRow = ImmutableList.of("2019-03-01", "F", "GBLO");
CsvRow row = CsvFile.of(headers, ImmutableList.of(firstRow)).row(0);
assertEquals(
CsvLoaderUtils.parseAdjustableDate(row, "DTE", "CNV", "CAL", Currency.EUR),
CsvLoaderUtils.parseAdjustableDate(row, "DTE", "CNV", "CAL", FOLLOWING, Currency.EUR),
AdjustableDate.of(
LocalDate.of(2019, 3, 1),
BusinessDayAdjustment.of(FOLLOWING, HolidayCalendarIds.GBLO)));
Expand All @@ -99,7 +99,7 @@ public void test_parseAdjustableDate_defaulting_noAdjustment() {
ImmutableList<String> firstRow = ImmutableList.of("2019-03-01");
CsvRow row = CsvFile.of(headers, ImmutableList.of(firstRow)).row(0);
assertEquals(
CsvLoaderUtils.parseAdjustableDate(row, "DTE", "CNV", "CAL", Currency.EUR),
CsvLoaderUtils.parseAdjustableDate(row, "DTE", "CNV", "CAL", FOLLOWING, Currency.EUR),
AdjustableDate.of(LocalDate.of(2019, 3, 1), BusinessDayAdjustment.of(FOLLOWING, EUTA)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeInfo;
import com.opengamma.strata.product.common.LongShort;
import com.opengamma.strata.product.credit.Cds;
import com.opengamma.strata.product.credit.CdsTrade;
import com.opengamma.strata.product.credit.PaymentOnDefault;
import com.opengamma.strata.product.credit.ProtectionStartOfDay;
import com.opengamma.strata.product.credit.type.CdsConventions;
import com.opengamma.strata.product.deposit.TermDeposit;
import com.opengamma.strata.product.deposit.TermDepositTrade;
import com.opengamma.strata.product.deposit.type.TermDepositConventions;
Expand Down Expand Up @@ -1391,14 +1396,117 @@ public void test_load_termDeposit() {
checkRoundtrip(TermDepositTrade.class, filtered, expected0, expected1, expected2);
}

//-------------------------------------------------------------------------
public void test_load_cds() {
TradeCsvLoader test = TradeCsvLoader.standard();
ValueWithFailures<List<Trade>> trades = test.load(FILE);

List<CdsTrade> filtered = trades.getValue().stream()
.flatMap(filtering(CdsTrade.class))
.collect(toImmutableList());
assertEquals(filtered.size(), 4);

CdsTrade expected0 = expectedCds0();
CdsTrade expected1 = expectedCds1();
CdsTrade expected2 = expectedCds2();
CdsTrade expected3 = expectedCds3();

assertBeanEquals(expected0, filtered.get(0));
assertBeanEquals(expected1, filtered.get(1));
assertBeanEquals(expected2, filtered.get(2));
assertBeanEquals(expected3, filtered.get(3));

checkRoundtrip(CdsTrade.class, filtered, expected0, expected1, expected2, expected3);
}

private CdsTrade expectedCds0() {
StandardId legEnt = StandardId.of("OG-Entity", "FOO");
return CdsConventions.GBP_STANDARD
.createTrade(legEnt, date(2017, 6, 1), Tenor.ofYears(5), BUY, 2_000_000, 0.005, REF_DATA)
.toBuilder()
.info(TradeInfo.builder()
.id(StandardId.of("OG", "123441"))
.tradeDate(date(2017, 6, 1))
.build())
.build();
}

private CdsTrade expectedCds1() {
StandardId legEnt = StandardId.of("BLUE", "BAR");
return CdsConventions.EUR_GB_STANDARD
.createTrade(legEnt, date(2017, 6, 1), date(2017, 6, 21), date(2019, 6, 19), SELL, 1_500_000, 0.011, REF_DATA)
.toBuilder()
.info(TradeInfo.builder()
.id(StandardId.of("OG", "123442"))
.tradeDate(date(2017, 6, 1))
.build())
.build();
}

private CdsTrade expectedCds2() {
return CdsTrade.builder()
.info(TradeInfo.builder()
.id(StandardId.of("OG", "123443"))
.tradeDate(date(2017, 6, 1))
.build())
.product(Cds.builder()
.buySell(BUY)
.legalEntityId(StandardId.of("BLUE", "CAT"))
.fixedRate(0.026)
.currency(EUR)
.notional(1_500_000)
.paymentSchedule(PeriodicSchedule.builder()
.startDate(date(2017, 6, 21))
.endDate(date(2019, 6, 19))
.frequency(Frequency.P3M)
.stubConvention(StubConvention.SMART_INITIAL)
.rollConvention(RollConventions.IMM)
.businessDayAdjustment(BusinessDayAdjustment.NONE)
.build())
.build())
.build();
}

private CdsTrade expectedCds3() {
return CdsTrade.builder()
.info(TradeInfo.builder()
.id(StandardId.of("OG", "123444"))
.tradeDate(date(2017, 6, 1))
.build())
.product(Cds.builder()
.buySell(BUY)
.legalEntityId(StandardId.of("BLUE", "CAT"))
.fixedRate(0.026)
.currency(EUR)
.notional(1_500_000)
.dayCount(DayCounts.ACT_365F)
.paymentOnDefault(PaymentOnDefault.NONE)
.protectionStart(ProtectionStartOfDay.NONE)
.paymentSchedule(PeriodicSchedule.builder()
.startDate(date(2017, 6, 21))
.endDate(date(2019, 6, 19))
.frequency(Frequency.P3M)
.stubConvention(StubConvention.SMART_FINAL)
.rollConvention(RollConventions.IMM)
.businessDayAdjustment(BusinessDayAdjustment.of(FOLLOWING, GBLO))
.build())
.stepinDateOffset(DaysAdjustment.ofBusinessDays(2, GBLO))
.settlementDateOffset(DaysAdjustment.ofBusinessDays(2, GBLO))
.build())
.upfrontFee(
AdjustablePayment.of(CurrencyAmount.of(GBP, -1000),
AdjustableDate.of(date(2017, 6, 3), BusinessDayAdjustment.of(MODIFIED_FOLLOWING, GBLO))))
.build();
}

//-------------------------------------------------------------------------
public void test_load_filtered() {
TradeCsvLoader test = TradeCsvLoader.standard();
ValueWithFailures<List<Trade>> trades = test.parse(
ImmutableList.of(FILE.getCharSource()), ImmutableList.of(FraTrade.class, TermDepositTrade.class));

assertEquals(trades.getValue().size(), 6);
assertEquals(trades.getFailures().size(), 13);
assertEquals(trades.getFailures().size(), 17);
assertEquals(trades.getFailures().get(0).getMessage(),
"Trade type not allowed " + SwapTrade.class.getName() + ", only these types are supported: FraTrade, TermDepositTrade");
}
Expand Down
Loading

0 comments on commit 19ee4f5

Please sign in to comment.