diff --git a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/CsvLoaderUtils.java b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/CsvLoaderUtils.java index e42bcf8bfe..655668394e 100644 --- a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/CsvLoaderUtils.java +++ b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/CsvLoaderUtils.java @@ -500,7 +500,7 @@ public static CurrencyAmount parseCurrencyAmountWithDirection( * @param directionField the direction field * @return if a valid currency amount can be read */ - public static boolean hasValidCurrencyAmount( + public static Optional tryParseCurrencyAmountWithDirection( CsvRow row, String currencyField, String amountField, @@ -510,7 +510,10 @@ public static boolean hasValidCurrencyAmount( Optional amount = row.findValue(amountField, LoaderUtils::parseDouble); Optional direction = row.findValue(directionField, LoaderUtils::parsePayReceive); - return (currency.isPresent() && amount.isPresent() && direction.isPresent()); + if (currency.isPresent() && amount.isPresent() && direction.isPresent()) { + return Optional.of(CurrencyAmount.of(currency.get(), direction.get().normalize(amount.get()))); + } + return Optional.empty(); } //------------------------------------------------------------------------- diff --git a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/FxSingleBarrierOptionTradeCsvPlugin.java b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/FxSingleBarrierOptionTradeCsvPlugin.java new file mode 100644 index 0000000000..ff0f21c28c --- /dev/null +++ b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/FxSingleBarrierOptionTradeCsvPlugin.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 - 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.BARRIER_LEVEL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.BARRIER_TYPE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.KNOCK_TYPE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.REBATE_AMOUNT_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.REBATE_CURRENCY_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.REBATE_DIRECTION_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.TRADE_TYPE_FIELD; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.collect.io.CsvOutput; +import com.opengamma.strata.collect.io.CsvRow; +import com.opengamma.strata.loader.LoaderUtils; +import com.opengamma.strata.product.Trade; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.fxopt.FxSingleBarrierOption; +import com.opengamma.strata.product.fxopt.FxSingleBarrierOptionTrade; +import com.opengamma.strata.product.fxopt.FxVanillaOptionTrade; +import com.opengamma.strata.product.option.BarrierType; +import com.opengamma.strata.product.option.KnockType; +import com.opengamma.strata.product.option.SimpleConstantContinuousBarrier; + +/** + * Handles the CSV files format for FX Single Barrier Option trades. + */ +public class FxSingleBarrierOptionTradeCsvPlugin implements TradeCsvParserPlugin, TradeCsvWriterPlugin { + + /** + * The singleton instance of the plugin. + */ + public static final FxSingleBarrierOptionTradeCsvPlugin INSTANCE = new FxSingleBarrierOptionTradeCsvPlugin(); + + /** + * The CSV headers. + */ + public static final Set HEADERS = ImmutableSet.builder() + .addAll(FxVanillaOptionTradeCsvPlugin.INSTANCE.headers(ImmutableList.of())) + .add(BARRIER_TYPE_FIELD) + .add(KNOCK_TYPE_FIELD) + .add(BARRIER_LEVEL_FIELD) + .add(REBATE_AMOUNT_FIELD) + .add(REBATE_CURRENCY_FIELD) + .add(REBATE_DIRECTION_FIELD) + .build(); + + /** + * Restricted constructor + */ + private FxSingleBarrierOptionTradeCsvPlugin() { + } + + @Override + public Set tradeTypeNames() { + return ImmutableSet.of("FXSINGLEBARRIEROPTION", "FX SINGLE BARRIER OPTION"); + } + + @Override + public Optional parseTrade( + Class requiredJavaType, + CsvRow baseRow, + List additionalRows, + TradeInfo info, + TradeCsvInfoResolver resolver) { + + if (requiredJavaType.isAssignableFrom(FxSingleBarrierOptionTrade.class)) { + return Optional.of(parse(baseRow, info, resolver)); + } + return Optional.empty(); + } + + private FxSingleBarrierOptionTrade parse(CsvRow row, TradeInfo info, TradeCsvInfoResolver resolver) { + FxVanillaOptionTrade vanillaOptionTrade = resolver.parseFxVanillaOptionTrade(row, info); + + BarrierType barrierType = row.getValue(BARRIER_TYPE_FIELD, LoaderUtils::parseBarrierType); + KnockType knockType = row.getValue(KNOCK_TYPE_FIELD, LoaderUtils::parseKnockType); + double barrierLevel = row.getValue(BARRIER_LEVEL_FIELD, LoaderUtils::parseDouble); + AdjustablePayment premium = CsvLoaderUtils.parsePremiumFromDefaultFields(row); + + FxSingleBarrierOption.Builder productBuilder = FxSingleBarrierOption.builder() + .underlyingOption(vanillaOptionTrade.getProduct()) + .barrier(SimpleConstantContinuousBarrier.of(barrierType, knockType, barrierLevel)); + + CsvLoaderUtils.tryParseCurrencyAmountWithDirection( + row, + REBATE_CURRENCY_FIELD, + REBATE_AMOUNT_FIELD, + REBATE_DIRECTION_FIELD + ).ifPresent(productBuilder::rebate); + + return FxSingleBarrierOptionTrade.builder() + .product(productBuilder.build()) + .premium(premium) + .info(info) + .build(); + } + + @Override + public String getName() { + return FxSingleBarrierOptionTrade.class.getSimpleName(); + } + + @Override + public Set> supportedTradeTypes() { + return ImmutableSet.of(FxSingleBarrierOptionTrade.class); + } + + @Override + public Set headers(List trades) { + return HEADERS; + } + + @Override + public void writeCsv(CsvOutput.CsvRowOutputWithHeaders csv, FxSingleBarrierOptionTrade trade) { + csv.writeCell(TRADE_TYPE_FIELD, "FxSingleBarrierOption"); + writeSingleBarrierOption(csv, trade.getProduct()); + CsvWriterUtils.writePremiumFields(csv, trade.getPremium()); + csv.writeNewLine(); + } + + void writeSingleBarrierOption(CsvOutput.CsvRowOutputWithHeaders csv, FxSingleBarrierOption product) { + CsvWriterUtils.writeFxVanillaOption(csv, product.getUnderlyingOption()); + CsvWriterUtils.writeBarrierFields(csv, product.getBarrier(), LocalDate.now(ZoneId.systemDefault())); + product.getRebate().ifPresent(ccyAmount -> CsvWriterUtils.writeCurrencyAmount( + csv, + ccyAmount, + REBATE_AMOUNT_FIELD, + REBATE_CURRENCY_FIELD, + REBATE_DIRECTION_FIELD)); + } +} diff --git a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini index a50faf62ea..57c69cc204 100644 --- a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini +++ b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini @@ -12,6 +12,7 @@ com.opengamma.strata.loader.csv.CdsTradeCsvPlugin = constants com.opengamma.strata.loader.csv.CdsIndexTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FraTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxNdfTradeCsvPlugin = constants +com.opengamma.strata.loader.csv.FxSingleBarrierOptionTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxSingleTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxSwapTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxVanillaOptionTradeCsvPlugin = constants diff --git a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini index bbd7bd2e5c..e14bc33b53 100644 --- a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini +++ b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini @@ -12,6 +12,7 @@ com.opengamma.strata.loader.csv.FraTradeCsvPlugin = constants com.opengamma.strata.loader.csv.TermDepositTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FullSwapTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxNdfTradeCsvPlugin = constants +com.opengamma.strata.loader.csv.FxSingleBarrierOptionTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxSingleTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxSwapTradeCsvPlugin = constants com.opengamma.strata.loader.csv.SwaptionTradeCsvPlugin = constants diff --git a/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java b/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java index 17f6b740b5..3826ec5f9f 100644 --- a/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java +++ b/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java @@ -28,6 +28,7 @@ import static com.opengamma.strata.collect.TestHelper.date; import static com.opengamma.strata.product.common.BuySell.BUY; import static com.opengamma.strata.product.common.BuySell.SELL; +import static com.opengamma.strata.product.common.LongShort.SHORT; import static com.opengamma.strata.product.common.PayReceive.PAY; import static com.opengamma.strata.product.common.PayReceive.RECEIVE; import static org.assertj.core.api.Assertions.assertThat; @@ -116,8 +117,13 @@ import com.opengamma.strata.product.fx.FxSingleTrade; import com.opengamma.strata.product.fx.FxSwap; import com.opengamma.strata.product.fx.FxSwapTrade; +import com.opengamma.strata.product.fxopt.FxSingleBarrierOption; +import com.opengamma.strata.product.fxopt.FxSingleBarrierOptionTrade; import com.opengamma.strata.product.fxopt.FxVanillaOption; import com.opengamma.strata.product.fxopt.FxVanillaOptionTrade; +import com.opengamma.strata.product.option.BarrierType; +import com.opengamma.strata.product.option.KnockType; +import com.opengamma.strata.product.option.SimpleConstantContinuousBarrier; import com.opengamma.strata.product.payment.BulletPayment; import com.opengamma.strata.product.payment.BulletPaymentTrade; import com.opengamma.strata.product.swap.CompoundingMethod; @@ -182,7 +188,7 @@ public void test_load_failures() { } @Test - public void test_load_fx_forwards() throws Exception { + public void test_load_fx_forwards() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades.csv"); ImmutableList charSources = ImmutableList.of(locator.getCharSource()); @@ -220,7 +226,7 @@ public void test_load_fx_forwards() throws Exception { } @Test - public void test_load_fx_forwards_with_legs_in_same_direction() throws Exception { + public void test_load_fx_forwards_with_legs_in_same_direction() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades_legs_same_direction.csv"); ValueWithFailures> loadedData = standard.load(locator); @@ -235,7 +241,7 @@ public void test_load_fx_forwards_with_legs_in_same_direction() throws Exception } @Test - public void test_load_fx_forwards_fullFormat() throws Exception { + public void test_load_fx_forwards_fullFormat() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades2.csv"); ImmutableList charSources = ImmutableList.of(locator.getCharSource()); @@ -311,7 +317,7 @@ public void test_load_fx_forwards_fullFormat() throws Exception { //------------------------------------------------------------------------- @Test - public void test_load_fx_swaps() throws Exception { + public void test_load_fx_swaps() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades.csv"); ImmutableList charSources = ImmutableList.of(locator.getCharSource()); @@ -349,7 +355,7 @@ public void test_load_fx_swaps() throws Exception { } @Test - public void test_load_fx_swaps_fullFormat() throws Exception { + public void test_load_fx_swaps_fullFormat() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades2.csv"); ImmutableList charSources = ImmutableList.of(locator.getCharSource()); @@ -379,7 +385,7 @@ public void test_load_fx_swaps_fullFormat() throws Exception { //------------------------------------------------------------------------- @Test - public void test_load_fx_vanilla_option() throws Exception { + public void test_load_fx_vanilla_option() { TradeCsvLoader standard = TradeCsvLoader.standard(); ResourceLocator locator = ResourceLocator.of("classpath:com/opengamma/strata/loader/csv/fxtrades.csv"); ImmutableList charSources = ImmutableList.of(locator.getCharSource()); @@ -1672,6 +1678,49 @@ private SwaptionTrade expectedSwaption2() { return SwaptionTrade.of(swapTrade.getInfo(), swaption, premium); } + private FxSingleTrade expectedFxSingle() { + double notional = 1.0e6; + double fxRate = 1.1d; + return FxSingleTrade.of( + TradeInfo.empty(), + FxSingle.of( + CurrencyAmount.of(EUR, notional), + CurrencyAmount.of(USD, -notional * fxRate), + LocalDate.of(2014, 5, 13))); + } + + private FxVanillaOptionTrade expectedFxVanillaOption() { + return FxVanillaOptionTrade.builder() + .product(FxVanillaOption.builder() + .longShort(SHORT) + .expiryDate(LocalDate.of(2014, 5, 9)) + .expiryTime(LocalTime.of(13, 10)) + .expiryZone(ZoneId.of("Z")) + .underlying(expectedFxSingle().getProduct()) + .build()) + .premium(AdjustablePayment.of(Payment.of(CurrencyAmount.of(USD, 230.3), LocalDate.of(2014, 1, 12)))) + .build(); + } + + private FxSingleBarrierOptionTrade expectedFxSingleBarrierOptionWithRebate() { + return FxSingleBarrierOptionTrade.builder() + .product(FxSingleBarrierOption.of( + expectedFxVanillaOption().getProduct(), + SimpleConstantContinuousBarrier.of(BarrierType.UP, KnockType.KNOCK_IN, 17.2), + CurrencyAmount.of(USD, 17.666))) + .premium(AdjustablePayment.of(Payment.of(CurrencyAmount.of(USD, 230.3), LocalDate.of(2014, 1, 12)))) + .build(); + } + + private FxSingleBarrierOptionTrade expectedFxSingleBarrierOptionWithoutRebate() { + return FxSingleBarrierOptionTrade.builder() + .product(FxSingleBarrierOption.of( + expectedFxVanillaOption().getProduct(), + SimpleConstantContinuousBarrier.of(BarrierType.DOWN, KnockType.KNOCK_OUT, 17.2))) + .premium(AdjustablePayment.of(Payment.of(CurrencyAmount.of(USD, 230.3), LocalDate.of(2014, 1, 12)))) + .build(); + } + //------------------------------------------------------------------------- @Test public void test_load_bulletPayment() { @@ -2072,6 +2121,22 @@ public void test_load_genericSecurity() { } //------------------------------------------------------------------------- + + @Test + public void test_FxSingleBarrierOption() { + ResourceLocator file = ResourceLocator.of( + "classpath:com/opengamma/strata/loader/csv/fx_single_barrier_option_trade.csv"); + + ValueWithFailures> trades = TradeCsvLoader.standard().parse( + ImmutableList.of(file.getCharSource()), FxSingleBarrierOptionTrade.class); + + checkRoundtrip( + FxSingleBarrierOptionTrade.class, + trades.getValue(), + expectedFxSingleBarrierOptionWithRebate(), + expectedFxSingleBarrierOptionWithoutRebate()); + } + @Test public void test_load_CapFloor() { TradeCsvLoader test = TradeCsvLoader.standard(); @@ -2331,6 +2396,7 @@ public void coverage() { coverPrivateConstructor(SwapTradeCsvPlugin.class); coverPrivateConstructor(TermDepositTradeCsvPlugin.class); coverPrivateConstructor(FullSwapTradeCsvPlugin.class); + coverPrivateConstructor(FxSingleBarrierOptionTradeCsvPlugin.class); } } diff --git a/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/fx_single_barrier_option_trade.csv b/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/fx_single_barrier_option_trade.csv new file mode 100644 index 0000000000..c953627031 --- /dev/null +++ b/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/fx_single_barrier_option_trade.csv @@ -0,0 +1,3 @@ +Strata Trade Type,Payment Date Convention,Payment Date Calendar,Long Short,Expiry Date,Expiry Time,Expiry Zone,Premium Date,Premium Date Convention,Premium Date Calendar,Premium Direction,Premium Currency,Premium Amount,Leg 1 Direction,Leg 1 Payment Date,Leg 1 Currency,Leg 1 Notional,Leg 2 Direction,Leg 2 Payment Date,Leg 2 Currency,Leg 2 Notional,Barrier Type,Knock Type,Barrier Level,Rebate Amount,Rebate Currency,Rebate Direction +FxSingleBarrierOption,,,Short,2014-05-09,13:10,Z,2014-01-12,NoAdjust,NoHolidays,Receive,USD,230.3,Receive,2014-05-13,EUR,1000000,Pay,2014-05-13,USD,-1100000,Up,KnockIn,17.2,17.666,USD,Receive +FxSingleBarrierOption,,,Short,2014-05-09,13:10,Z,2014-01-12,NoAdjust,NoHolidays,Receive,USD,230.3,Receive,2014-05-13,EUR,1000000,Pay,2014-05-13,USD,-1100000,Down,KnockOut,17.2,,,