All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.loader.csv.SwapTradeCsvPlugin Maven / Gradle / Ivy

There is a newer version: 2.12.46
Show newest version
/*
 * Copyright (C) 2017 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.loader.csv;

import static com.opengamma.strata.loader.csv.CsvLoaderColumns.BUY_SELL_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.CONVENTION_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DATE_ADJ_CAL_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DATE_ADJ_CNV_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DIRECTION_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.END_DATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.FIRST_REGULAR_START_DATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.FIXED_RATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.FX_RATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.KNOWN_AMOUNT_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.LAST_REGULAR_END_DATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.NOTIONAL_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PERIOD_TO_START_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.ROLL_CONVENTION_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.START_DATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.STUB_CONVENTION_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.TENOR_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.TRADE_DATE_FIELD;
import static com.opengamma.strata.loader.csv.CsvLoaderColumns.TRADE_TYPE_FIELD;

import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.opengamma.strata.basics.ReferenceData;
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.HolidayCalendarId;
import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.basics.schedule.PeriodicSchedule;
import com.opengamma.strata.basics.schedule.RollConvention;
import com.opengamma.strata.basics.schedule.StubConvention;
import com.opengamma.strata.basics.value.ValueAdjustment;
import com.opengamma.strata.basics.value.ValueSchedule;
import com.opengamma.strata.basics.value.ValueStep;
import com.opengamma.strata.collect.io.CsvRow;
import com.opengamma.strata.collect.result.ParseFailureException;
import com.opengamma.strata.loader.LoaderUtils;
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeInfo;
import com.opengamma.strata.product.common.BuySell;
import com.opengamma.strata.product.swap.FixedRateCalculation;
import com.opengamma.strata.product.swap.KnownAmountSwapLeg;
import com.opengamma.strata.product.swap.NotionalSchedule;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.SwapTrade;
import com.opengamma.strata.product.swap.type.SingleCurrencySwapConvention;
import com.opengamma.strata.product.swap.type.XCcyIborIborSwapConvention;

/**
 * Loads Swap trades from CSV files.
 */
final class SwapTradeCsvPlugin implements TradeCsvParserPlugin {

  /**
   * The singleton instance of the plugin.
   */
  public static final SwapTradeCsvPlugin INSTANCE = new SwapTradeCsvPlugin();

  //-------------------------------------------------------------------------
  @Override
  public Set tradeTypeNames() {
    return ImmutableSet.of("SWAP");
  }

  @Override
  public boolean isAdditionalRow(CsvRow baseRow, CsvRow additionalRow) {
    return additionalRow.getField(TRADE_TYPE_FIELD).toUpperCase(Locale.ENGLISH).equals("VARIABLE");
  }

  @Override
  public Optional parseTrade(
      Class requiredJavaType,
      CsvRow baseRow,
      List additionalRows,
      TradeInfo info,
      TradeCsvInfoResolver resolver) {

    if (requiredJavaType.isAssignableFrom(SwapTrade.class)) {
      return Optional.of(resolver.parseSwapTrade(baseRow, additionalRows, info));
    }
    return Optional.empty();
  }

  @Override
  public String getName() {
    return "Swap";
  }

  //-------------------------------------------------------------------------
  /**
   * Parses from the CSV row.
   * 
   * @param row  the CSV row
   * @param info  the trade info
   * @param resolver  the resolver used to parse additional information
   * @return the parsed trade
   */
  static SwapTrade parse(CsvRow row, List variableRows, TradeInfo info, TradeCsvInfoResolver resolver) {
    SwapTrade trade = parseSwap(row, variableRows, info, resolver.getReferenceData());
    return resolver.completeTrade(row, trade);
  }

  // parses the swap without resolving it
  static SwapTrade parseSwap(CsvRow row, List variableRows, TradeInfo info, ReferenceData refData) {
    SwapTrade trade = parseRow(row, info, refData);
    trade = parseVariableNotional(trade, variableRows);
    trade = parseVariableFixedRate(trade, variableRows);
    trade = parseVariableKnownAmount(trade, variableRows);
    return trade;
  }

  // variable notional
  private static SwapTrade parseVariableNotional(SwapTrade trade, List variableRows) {
    ImmutableList legs = trade.getProduct().getLegs();
    ListMultimap steps =
        extractSteps(variableRows, legs, NOTIONAL_FIELD, str -> LoaderUtils.parseDouble(str));
    if (steps.isEmpty()) {
      return trade;
    }
    // adjust the trade, inserting the variable element
    ImmutableList.Builder legBuilder = ImmutableList.builder();
    for (int i = 0; i < legs.size(); i++) {
      SwapLeg swapLeg = legs.get(i);
      List legSteps = steps.get(i);
      if (!legSteps.isEmpty() && swapLeg instanceof RateCalculationSwapLeg) {
        RateCalculationSwapLeg leg = (RateCalculationSwapLeg) swapLeg;
        NotionalSchedule notionalSchedule = leg.getNotionalSchedule().toBuilder()
            .amount(ValueSchedule.of(leg.getNotionalSchedule().getAmount().getInitialValue(), legSteps))
            .build();
        swapLeg = leg.toBuilder().notionalSchedule(notionalSchedule).build();
      }
      legBuilder.add(swapLeg);
    }
    return replaceLegs(trade, legBuilder.build());
  }

  // variable fixed rate
  private static SwapTrade parseVariableFixedRate(SwapTrade trade, List variableRows) {
    ImmutableList legs = trade.getProduct().getLegs();
    ListMultimap steps =
        extractSteps(variableRows, legs, FIXED_RATE_FIELD, str -> LoaderUtils.parseDoublePercent(str));
    if (steps.isEmpty()) {
      return trade;
    }
    // adjust the trade, inserting the variable element
    ImmutableList.Builder legBuilder = ImmutableList.builder();
    for (int i = 0; i < legs.size(); i++) {
      SwapLeg swapLeg = legs.get(i);
      List legSteps = steps.get(i);
      if (!legSteps.isEmpty() && swapLeg instanceof RateCalculationSwapLeg) {
        RateCalculationSwapLeg leg = (RateCalculationSwapLeg) swapLeg;
        if (leg.getCalculation() instanceof FixedRateCalculation) {
          FixedRateCalculation baseCalc = (FixedRateCalculation) leg.getCalculation();
          swapLeg = leg.toBuilder()
              .calculation(baseCalc.toBuilder()
                  .rate(ValueSchedule.of(baseCalc.getRate().getInitialValue(), legSteps))
                  .build())
              .build();
        }
      }
      legBuilder.add(swapLeg);
    }
    return replaceLegs(trade, legBuilder.build());
  }

  // variable known amount
  private static SwapTrade parseVariableKnownAmount(SwapTrade trade, List variableRows) {
    ImmutableList legs = trade.getProduct().getLegs();
    ListMultimap steps =
        extractSteps(variableRows, legs, KNOWN_AMOUNT_FIELD, str -> LoaderUtils.parseDouble(str));
    if (steps.isEmpty()) {
      return trade;
    }
    // adjust the trade, inserting the variable element
    ImmutableList.Builder legBuilder = ImmutableList.builder();
    for (int i = 0; i < legs.size(); i++) {
      SwapLeg swapLeg = legs.get(i);
      List legSteps = steps.get(i);
      if (!legSteps.isEmpty() && swapLeg instanceof KnownAmountSwapLeg) {
        KnownAmountSwapLeg leg = (KnownAmountSwapLeg) swapLeg;
        swapLeg = leg.toBuilder()
            .amount(ValueSchedule.of(leg.getAmount().getInitialValue(), legSteps))
            .build();
      }
      legBuilder.add(swapLeg);
    }
    return replaceLegs(trade, legBuilder.build());
  }

  // extract the steps for a field, either 'Leg n Foo' or 'Foo'
  private static ListMultimap extractSteps(
      List variableRows,
      ImmutableList legs,
      String field,
      Function parser) {

    ListMultimap steps = ArrayListMultimap.create();
    for (CsvRow row : variableRows) {
      LocalDate date = row.getValue(START_DATE_FIELD, LoaderUtils::parseDate);
      for (int i = 0; i < legs.size(); i++) {
        int legIndex = i;  // must be effectively final for lambda
        row.findValue("Leg " + (legIndex + 1) + " " + field)
            .map(Optional::of)
            .orElseGet(() -> row.findValue(field))
            .map(parser)
            .map(val -> ValueStep.of(date, ValueAdjustment.ofReplace(val)))
            .ifPresent(step -> steps.put(legIndex, step));
      }
    }
    return steps;
  }

  //-------------------------------------------------------------------------
  // parse the row to a trade
  private static SwapTrade parseRow(CsvRow row, TradeInfo info, ReferenceData refData) {
    Optional conventionOpt = row.findValue(CONVENTION_FIELD);
    if (conventionOpt.isPresent()) {
      return parseWithConvention(row, info, refData, conventionOpt.get());
    } else {
      Optional payReceive = row.findValue("Leg 1 " + DIRECTION_FIELD);
      if (payReceive.isPresent()) {
        return FullSwapTradeCsvPlugin.parse(row, info);
      }
      throw new ParseFailureException(
          "Swap trade had invalid combination of fields. Must include either '" +
              CONVENTION_FIELD + "' or '" + "Leg 1 " + DIRECTION_FIELD + "'");
    }
  }

  // parse a trade based on a convention
  static SwapTrade parseWithConvention(CsvRow row, TradeInfo info, ReferenceData refData, String conventionStr) {
    BuySell buySell = row.getValue(BUY_SELL_FIELD, LoaderUtils::parseBuySell);
    double notional = row.getValue(NOTIONAL_FIELD, LoaderUtils::parseDouble);
    double fixedRate = row.getValue(FIXED_RATE_FIELD, LoaderUtils::parseDoublePercent);
    Optional periodToStartOpt = row.findValue(PERIOD_TO_START_FIELD).map(s -> LoaderUtils.parsePeriod(s));
    Optional tenorOpt = row.findValue(TENOR_FIELD).map(s -> LoaderUtils.parseTenor(s));
    Optional startDateOpt = row.findValue(START_DATE_FIELD).map(s -> LoaderUtils.parseDate(s));
    Optional endDateOpt = row.findValue(END_DATE_FIELD).map(s -> LoaderUtils.parseDate(s));
    Optional rollCnvOpt = row.findValue(ROLL_CONVENTION_FIELD).map(s -> LoaderUtils.parseRollConvention(s));
    Optional stubCnvOpt = row.findValue(STUB_CONVENTION_FIELD).map(s -> StubConvention.of(s));
    Optional firstRegStartDateOpt =
        row.findValue(FIRST_REGULAR_START_DATE_FIELD).map(s -> LoaderUtils.parseDate(s));
    Optional lastRegEndDateOpt = row.findValue(LAST_REGULAR_END_DATE_FIELD).map(s -> LoaderUtils.parseDate(s));
    BusinessDayConvention dateCnv = row.findValue(DATE_ADJ_CNV_FIELD)
        .map(s -> LoaderUtils.parseBusinessDayConvention(s)).orElse(BusinessDayConventions.MODIFIED_FOLLOWING);
    Optional dateCalOpt = row.findValue(DATE_ADJ_CAL_FIELD).map(s -> HolidayCalendarId.of(s));
    Optional fxRateOpt = row.findValue(FX_RATE_FIELD).map(str -> LoaderUtils.parseDouble(str));

    // explicit dates take precedence over relative ones
    if (startDateOpt.isPresent() && endDateOpt.isPresent()) {
      if (periodToStartOpt.isPresent() || tenorOpt.isPresent()) {
        throw new ParseFailureException(
            "Swap trade had invalid combination of fields. When these fields are found " +
                ImmutableList.of(CONVENTION_FIELD, START_DATE_FIELD, END_DATE_FIELD) +
                " then these fields must not be present " +
                ImmutableList.of(PERIOD_TO_START_FIELD, TENOR_FIELD));
      }
      LocalDate startDate = startDateOpt.get();
      LocalDate endDate = endDateOpt.get();
      SwapTrade trade = createSwap(info, conventionStr, startDate, endDate, buySell, notional, fixedRate, fxRateOpt);
      return adjustTrade(trade, rollCnvOpt, stubCnvOpt, firstRegStartDateOpt, lastRegEndDateOpt, dateCnv, dateCalOpt);
    }

    // start date + tenor
    if (startDateOpt.isPresent() && tenorOpt.isPresent()) {
      if (periodToStartOpt.isPresent() || endDateOpt.isPresent()) {
        throw new ParseFailureException(
            "Swap trade had invalid combination of fields. When these fields are found " +
                ImmutableList.of(CONVENTION_FIELD, START_DATE_FIELD, TENOR_FIELD) +
                " then these fields must not be present " +
                ImmutableList.of(PERIOD_TO_START_FIELD, END_DATE_FIELD));
      }
      LocalDate startDate = startDateOpt.get();
      Tenor tenor = tenorOpt.get();
      LocalDate endDate = startDate.plus(tenor);
      SwapTrade trade = createSwap(info, conventionStr, startDate, endDate, buySell, notional, fixedRate, fxRateOpt);
      return adjustTrade(trade, rollCnvOpt, stubCnvOpt, firstRegStartDateOpt, lastRegEndDateOpt, dateCnv, dateCalOpt);
    }

    // relative dates
    if (periodToStartOpt.isPresent() && tenorOpt.isPresent() && info.getTradeDate().isPresent()) {
      if (startDateOpt.isPresent() || endDateOpt.isPresent()) {
        throw new ParseFailureException(
            "Swap trade had invalid combination of fields. When these fields are found " +
                ImmutableList.of(CONVENTION_FIELD, PERIOD_TO_START_FIELD, TENOR_FIELD, TRADE_DATE_FIELD) +
                " then these fields must not be present " +
                ImmutableList.of(START_DATE_FIELD, END_DATE_FIELD));
      }
      LocalDate tradeDate = info.getTradeDate().get();
      Period periodToStart = periodToStartOpt.get();
      Tenor tenor = tenorOpt.get();
      if (fxRateOpt.isPresent()) {
        XCcyIborIborSwapConvention convention = XCcyIborIborSwapConvention.of(conventionStr);
        double notionalFlat = notional * fxRateOpt.get();
        SwapTrade trade = convention.createTrade(
            tradeDate, periodToStart, tenor, buySell, notional, notionalFlat, fixedRate, refData);
        trade = trade.toBuilder().info(info).build();
        return adjustTrade(trade, rollCnvOpt, stubCnvOpt, firstRegStartDateOpt, lastRegEndDateOpt, dateCnv, dateCalOpt);
      } else {
        SingleCurrencySwapConvention convention = SingleCurrencySwapConvention.of(conventionStr);
        SwapTrade trade = convention.createTrade(
            tradeDate, periodToStart, tenor, buySell, notional, fixedRate, refData);
        trade = trade.toBuilder().info(info).build();
        return adjustTrade(trade, rollCnvOpt, stubCnvOpt, firstRegStartDateOpt, lastRegEndDateOpt, dateCnv, dateCalOpt);
      }
    }

    // no match
    throw new ParseFailureException(
        "Swap trade had invalid combination of fields. These fields are mandatory:" +
            ImmutableList.of(BUY_SELL_FIELD, NOTIONAL_FIELD, FIXED_RATE_FIELD) +
            " and one of these combinations is mandatory: " +
            ImmutableList.of(CONVENTION_FIELD, TRADE_DATE_FIELD, PERIOD_TO_START_FIELD, TENOR_FIELD) +
            " or " +
            ImmutableList.of(CONVENTION_FIELD, START_DATE_FIELD, TENOR_FIELD) +
            " or " +
            ImmutableList.of(CONVENTION_FIELD, START_DATE_FIELD, END_DATE_FIELD));
  }

  // create a swap from known start/end dates
  private static SwapTrade createSwap(
      TradeInfo info,
      String conventionStr,
      LocalDate startDate,
      LocalDate endDate,
      BuySell buySell,
      double notional,
      double fixedRate,
      Optional fxRateOpt) {

    if (fxRateOpt.isPresent()) {
      XCcyIborIborSwapConvention convention = XCcyIborIborSwapConvention.of(conventionStr);
      double notionalFlat = notional * fxRateOpt.get();
      return convention.toTrade(info, startDate, endDate, buySell, notional, notionalFlat, fixedRate);
    } else {
      SingleCurrencySwapConvention convention = SingleCurrencySwapConvention.of(conventionStr);
      return convention.toTrade(info, startDate, endDate, buySell, notional, fixedRate);
    }
  }

  // adjust trade based on additional fields specified
  private static SwapTrade adjustTrade(
      SwapTrade trade,
      Optional rollConventionOpt,
      Optional stubConventionOpt,
      Optional firstRegularStartDateOpt,
      Optional lastRegEndDateOpt,
      BusinessDayConvention dateCnv,
      Optional dateCalOpt) {

    ImmutableList.Builder legBuilder = ImmutableList.builder();
    for (SwapLeg leg : trade.getProduct().getLegs()) {
      RateCalculationSwapLeg swapLeg = (RateCalculationSwapLeg) leg;
      PeriodicSchedule.Builder scheduleBuilder = swapLeg.getAccrualSchedule().toBuilder();
      scheduleBuilder.stubConvention(stubConventionOpt.orElse(StubConvention.SMART_INITIAL));
      rollConventionOpt.ifPresent(rc -> scheduleBuilder.rollConvention(rc));
      firstRegularStartDateOpt.ifPresent(date -> scheduleBuilder.firstRegularStartDate(date));
      lastRegEndDateOpt.ifPresent(date -> scheduleBuilder.lastRegularEndDate(date));
      dateCalOpt.ifPresent(cal -> scheduleBuilder.businessDayAdjustment(BusinessDayAdjustment.of(dateCnv, cal)));
      legBuilder.add(swapLeg.toBuilder()
          .accrualSchedule(scheduleBuilder.build())
          .build());
    }
    return replaceLegs(trade, legBuilder.build());
  }

  // replace the legs
  private static SwapTrade replaceLegs(SwapTrade trade, ImmutableList legs) {
    return trade.toBuilder()
        .product(trade.getProduct().toBuilder()
            .legs(legs)
            .build())
        .build();
  }

  //-------------------------------------------------------------------------
  // Restricted constructor.
  private SwapTradeCsvPlugin() {
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy