From cfa248e5da9694018efc957d2de57cb8c6bed547 Mon Sep 17 00:00:00 2001 From: Yuki Iwashita Date: Wed, 16 Oct 2024 09:55:48 +0100 Subject: [PATCH] Overnight Rate Cap Floor Model (#2677) * overnight cap product * tests * javadoc * line * clean * white spaces * failing test for accrual --- .../opengamma/strata/product/ProductType.java | 7 + .../capfloor/OvernightInArrearsCapFloor.java | 398 ++++++++ .../OvernightInArrearsCapFloorLeg.java | 888 ++++++++++++++++++ .../OvernightInArrearsCapFloorTrade.java | 516 ++++++++++ ...vernightInArrearsCapletFloorletPeriod.java | 2 +- .../ResolvedOvernightInArrearsCapFloor.java | 420 +++++++++ ...ResolvedOvernightInArrearsCapFloorLeg.java | 468 +++++++++ ...solvedOvernightInArrearsCapFloorTrade.java | 449 +++++++++ .../OvernightInArrearsCapFloorLegTest.java | 305 ++++++ .../OvernightInArrearsCapFloorTest.java | 167 ++++ .../OvernightInArrearsCapFloorTradeTest.java | 194 ++++ ...lvedOvernightInArrearsCapFloorLegTest.java | 186 ++++ ...esolvedOvernightInArrearsCapFloorTest.java | 176 ++++ ...edOvernightInArrearsCapFloorTradeTest.java | 80 ++ 14 files changed, 4255 insertions(+), 1 deletion(-) create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java diff --git a/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java b/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java index 270f7fd3ce..5d37f1aa5d 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java @@ -16,6 +16,7 @@ import com.opengamma.strata.product.bond.CapitalIndexedBond; import com.opengamma.strata.product.bond.FixedCouponBond; import com.opengamma.strata.product.capfloor.IborCapFloor; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloor; import com.opengamma.strata.product.cms.Cms; import com.opengamma.strata.product.credit.Cds; import com.opengamma.strata.product.credit.CdsIndex; @@ -110,6 +111,12 @@ public final class ProductType * A {@link IborCapFloor}. */ public static final ProductType IBOR_CAP_FLOOR = ProductType.of("IborCapFloor", "Cap/Floor"); + /** + * A {@link OvernightInArrearsCapFloor}. + */ + public static final ProductType OVERNIGHT_IN_ARREARS_CAP_FLOOR = ProductType.of( + "OvernightInArrearsCapFloor", + "Overnight In Arrears Cap/Floor"); /** * A {@link IborFuture}. */ diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java new file mode 100644 index 0000000000..e47673bb9d --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.BeanBuilder; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableValidator; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; +import org.joda.beans.impl.direct.DirectPrivateBeanBuilder; + +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.Resolvable; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.Index; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.Product; +import com.opengamma.strata.product.swap.SwapLeg; + +/** + * An overnight rate in arrears cap/floor product. + *

+ * The overnight rate in arrears cap/floor product consists of two legs, a cap/floor leg and a pay leg. + * The cap/floor leg involves a set of call/put options on successive compounded overnight index rates. + * The pay leg is any swap leg from a standard interest rate swap. + * The pay leg is absent for typical overnight rate in arrears cap/floor products, + * with the premium paid upfront instead, as defined in {@link OvernightInArrearsCapFloorTrade}. + */ +@BeanDefinition(builderScope = "private") +public final class OvernightInArrearsCapFloor + implements Product, Resolvable, ImmutableBean, Serializable { + + /** + * The cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are caplets or floorlets. + */ + @PropertyDefinition(validate = "notNull") + private final OvernightInArrearsCapFloorLeg capFloorLeg; + /** + * The optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. + * Instead the premium is paid upfront. + */ + @PropertyDefinition(get = "optional") + private final SwapLeg payLeg; + + //------------------------------------------------------------------------- + /** + * Obtains an instance from a cap/floor leg with no pay leg. + *

+ * The pay leg is absent in the resulting cap/floor. + * + * @param capFloorLeg the cap/floor leg + * @return the cap/floor + */ + public static OvernightInArrearsCapFloor of(OvernightInArrearsCapFloorLeg capFloorLeg) { + return new OvernightInArrearsCapFloor(capFloorLeg, null); + } + + /** + * Obtains an instance from a cap/floor leg and a pay leg. + * + * @param capFloorLeg the cap/floor leg + * @param payLeg the pay leg + * @return the cap/floor + */ + public static OvernightInArrearsCapFloor of(OvernightInArrearsCapFloorLeg capFloorLeg, SwapLeg payLeg) { + return new OvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + @ImmutableValidator + private void validate() { + if (payLeg != null) { + ArgChecker.isFalse( + payLeg.getPayReceive().equals(capFloorLeg.getPayReceive()), + "Legs must have different Pay/Receive flag, but both were {}", payLeg.getPayReceive()); + } + } + + //------------------------------------------------------------------------- + @Override + public ImmutableSet allPaymentCurrencies() { + if (payLeg == null) { + return ImmutableSet.of(capFloorLeg.getCurrency()); + } + return ImmutableSet.of(capFloorLeg.getCurrency(), payLeg.getCurrency()); + } + + @Override + public ImmutableSet allCurrencies() { + if (payLeg == null) { + return ImmutableSet.of(capFloorLeg.getCurrency()); + } + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCurrency()); + builder.addAll(payLeg.allCurrencies()); + return builder.build(); + } + + /** + * Returns the set of indices referred to by the cap/floor. + *

+ * A cap/floor will typically refer to one index, such as 'GBP-SONIA'. + * Calling this method will return the complete list of indices. + * + * @return the set of indices referred to by this cap/floor + */ + public ImmutableSet allIndices() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCalculation().getIndex()); + if (payLeg != null) { + payLeg.collectIndices(builder); + } + return builder.build(); + } + + //------------------------------------------------------------------------- + @Override + public ResolvedOvernightInArrearsCapFloor resolve(ReferenceData refData) { + if (payLeg == null) { + return ResolvedOvernightInArrearsCapFloor.of(capFloorLeg.resolve(refData)); + } + return ResolvedOvernightInArrearsCapFloor.of(capFloorLeg.resolve(refData), payLeg.resolve(refData)); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloor}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloor.Meta meta() { + return OvernightInArrearsCapFloor.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloor.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + private OvernightInArrearsCapFloor( + OvernightInArrearsCapFloorLeg capFloorLeg, + SwapLeg payLeg) { + JodaBeanUtils.notNull(capFloorLeg, "capFloorLeg"); + this.capFloorLeg = capFloorLeg; + this.payLeg = payLeg; + validate(); + } + + @Override + public OvernightInArrearsCapFloor.Meta metaBean() { + return OvernightInArrearsCapFloor.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are caplets or floorlets. + * @return the value of the property, not null + */ + public OvernightInArrearsCapFloorLeg getCapFloorLeg() { + return capFloorLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. + * Instead the premium is paid upfront. + * @return the optional value of the property, not null + */ + public Optional getPayLeg() { + return Optional.ofNullable(payLeg); + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloor other = (OvernightInArrearsCapFloor) obj; + return JodaBeanUtils.equal(capFloorLeg, other.capFloorLeg) && + JodaBeanUtils.equal(payLeg, other.payLeg); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(capFloorLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(payLeg); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("OvernightInArrearsCapFloor{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloor}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code capFloorLeg} property. + */ + private final MetaProperty capFloorLeg = DirectMetaProperty.ofImmutable( + this, "capFloorLeg", OvernightInArrearsCapFloor.class, OvernightInArrearsCapFloorLeg.class); + /** + * The meta-property for the {@code payLeg} property. + */ + private final MetaProperty payLeg = DirectMetaProperty.ofImmutable( + this, "payLeg", OvernightInArrearsCapFloor.class, SwapLeg.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "capFloorLeg", + "payLeg"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public BeanBuilder builder() { + return new OvernightInArrearsCapFloor.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloor.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code capFloorLeg} property. + * @return the meta-property, not null + */ + public MetaProperty capFloorLeg() { + return capFloorLeg; + } + + /** + * The meta-property for the {@code payLeg} property. + * @return the meta-property, not null + */ + public MetaProperty payLeg() { + return payLeg; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return ((OvernightInArrearsCapFloor) bean).getCapFloorLeg(); + case -995239866: // payLeg + return ((OvernightInArrearsCapFloor) bean).payLeg; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloor}. + */ + private static final class Builder extends DirectPrivateBeanBuilder { + + private OvernightInArrearsCapFloorLeg capFloorLeg; + private SwapLeg payLeg; + + /** + * Restricted constructor. + */ + private Builder() { + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + this.capFloorLeg = (OvernightInArrearsCapFloorLeg) newValue; + break; + case -995239866: // payLeg + this.payLeg = (SwapLeg) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public OvernightInArrearsCapFloor build() { + return new OvernightInArrearsCapFloor( + capFloorLeg, + payLeg); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("OvernightInArrearsCapFloor.Builder{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java new file mode 100644 index 0000000000..af60401eb9 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableList; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.Resolvable; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.date.AdjustableDate; +import com.opengamma.strata.basics.date.DateAdjuster; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.schedule.Schedule; +import com.opengamma.strata.basics.schedule.SchedulePeriod; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.collect.array.DoubleArray; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.OvernightAccrualMethod; +import com.opengamma.strata.product.swap.OvernightRateCalculation; +import com.opengamma.strata.product.swap.RateAccrualPeriod; + +/** + * An overnight rate in arrears cap/floor leg of a cap/floor product. + *

+ * This defines a single cap/floor leg for an overnight rate in arrears cap/floor product. + * The cap/floor instruments are defined as a set of call/put options on successive compounded overnight index rates. + *

+ * The periodic payments in the resolved leg are caplets or floorlets depending on the data in this leg. + * The {@code capSchedule} field is used to represent strike values of individual caplets, + * whereas {@code floorSchedule} is used to represent strike values of individual floorlets. + * Either {@code capSchedule} or {@code floorSchedule} must be present, and not both. + */ +@BeanDefinition +public final class OvernightInArrearsCapFloorLeg + implements Resolvable, ImmutableBean, Serializable { + + /** + * Whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + */ + @PropertyDefinition(validate = "notNull") + private final PayReceive payReceive; + /** + * The periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + */ + @PropertyDefinition(validate = "notNull") + private final PeriodicSchedule paymentSchedule; + /** + * The offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + */ + @PropertyDefinition(validate = "notNull") + private final DaysAdjustment paymentDateOffset; + /** + * The currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + */ + @PropertyDefinition(validate = "notNull") + private final Currency currency; + /** + * The notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + */ + @PropertyDefinition(validate = "notNull") + private final ValueSchedule notional; + /** + * The interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + */ + @PropertyDefinition(validate = "notNull") + private final OvernightRateCalculation calculation; + /** + * The cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + */ + @PropertyDefinition(get = "optional") + private final ValueSchedule capSchedule; + /** + * The floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + */ + @PropertyDefinition(get = "optional") + private final ValueSchedule floorSchedule; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.paymentDateOffset(DaysAdjustment.NONE); + } + + @ImmutableConstructor + private OvernightInArrearsCapFloorLeg( + PayReceive payReceive, + PeriodicSchedule paymentSchedule, + DaysAdjustment paymentDateOffset, + Currency currency, + ValueSchedule notional, + OvernightRateCalculation calculation, + ValueSchedule capSchedule, + ValueSchedule floorSchedule) { + + this.payReceive = ArgChecker.notNull(payReceive, "payReceive"); + this.paymentSchedule = ArgChecker.notNull(paymentSchedule, "paymentSchedule"); + this.paymentDateOffset = ArgChecker.notNull(paymentDateOffset, "paymentDateOffset"); + this.currency = currency != null ? currency : calculation.getIndex().getCurrency(); + this.notional = notional; + this.calculation = ArgChecker.notNull(calculation, "calculation"); + this.capSchedule = capSchedule; + this.floorSchedule = floorSchedule; + ArgChecker.isTrue(!this.getPaymentSchedule().getStubConvention().isPresent() || + this.getPaymentSchedule().getStubConvention().get().equals(StubConvention.NONE), "Stub period is not allowed"); + ArgChecker.isFalse(this.getCapSchedule().isPresent() == this.getFloorSchedule().isPresent(), + "One of cap schedule and floor schedule should be empty"); + ArgChecker.isTrue(this.getCalculation().getAccrualMethod().equals(OvernightAccrualMethod.COMPOUNDED), + "Overnight accrual method should be compounded"); + } + + //------------------------------------------------------------------------- + /** + * Gets the accrual start date of the leg. + *

+ * This is the first accrual date in the leg, often known as the effective date. + * + * @return the start date of the leg + */ + public AdjustableDate getStartDate() { + return paymentSchedule.calculatedStartDate(); + } + + /** + * Gets the accrual end date of the leg. + *

+ * This is the last accrual date in the leg, often known as the termination date. + * + * @return the end date of the leg + */ + public AdjustableDate getEndDate() { + return paymentSchedule.calculatedEndDate(); + } + + /** + * Gets the overnight index. + *

+ * The rate to be paid is based on this index + * It will be a well known market index such as 'GBP-SONIA'. + * + * @return the overnight index + */ + public OvernightIndex getIndex() { + return calculation.getIndex(); + } + + //------------------------------------------------------------------------- + @Override + public ResolvedOvernightInArrearsCapFloorLeg resolve(ReferenceData refData) { + Schedule adjustedSchedule = paymentSchedule.createSchedule(refData); + DoubleArray cap = getCapSchedule().isPresent() ? capSchedule.resolveValues(adjustedSchedule) : null; + DoubleArray floor = getFloorSchedule().isPresent() ? floorSchedule.resolveValues(adjustedSchedule) : null; + DoubleArray notionals = notional.resolveValues(adjustedSchedule); + List accrualPeriods = calculation.createAccrualPeriods(adjustedSchedule, adjustedSchedule, refData); + DateAdjuster paymentDateAdjuster = paymentDateOffset.resolve(refData); + ImmutableList.Builder periodsBuild = ImmutableList.builder(); + for (int i = 0; i < adjustedSchedule.size(); i++) { + SchedulePeriod period = adjustedSchedule.getPeriod(i); + LocalDate paymentDate = paymentDateAdjuster.adjust(period.getEndDate()); + double signedNotional = payReceive.normalize(notionals.get(i)); + periodsBuild.add(OvernightInArrearsCapletFloorletPeriod.builder() + .unadjustedStartDate(period.getUnadjustedStartDate()) + .unadjustedEndDate(period.getUnadjustedEndDate()) + .startDate(period.getStartDate()) + .endDate(period.getEndDate()) + .overnightRate((OvernightCompoundedRateComputation) accrualPeriods.get(i).getRateComputation()) // arg checked + .paymentDate(paymentDate) + .notional(signedNotional) + .currency(currency) + .yearFraction(period.yearFraction(calculation.getDayCount(), adjustedSchedule)) + .caplet(cap != null ? cap.get(i) : null) + .floorlet(floor != null ? floor.get(i) : null) + .build()); + } + return ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periodsBuild.build()) + .payReceive(payReceive) + .build(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorLeg}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloorLeg.Meta meta() { + return OvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloorLeg.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapFloorLeg.Builder builder() { + return new OvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public OvernightInArrearsCapFloorLeg.Meta metaBean() { + return OvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + * @return the value of the property, not null + */ + public PayReceive getPayReceive() { + return payReceive; + } + + //----------------------------------------------------------------------- + /** + * Gets the periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + * @return the value of the property, not null + */ + public PeriodicSchedule getPaymentSchedule() { + return paymentSchedule; + } + + //----------------------------------------------------------------------- + /** + * Gets the offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + * @return the value of the property, not null + */ + public DaysAdjustment getPaymentDateOffset() { + return paymentDateOffset; + } + + //----------------------------------------------------------------------- + /** + * Gets the currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + * @return the value of the property, not null + */ + public Currency getCurrency() { + return currency; + } + + //----------------------------------------------------------------------- + /** + * Gets the notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + * @return the value of the property, not null + */ + public ValueSchedule getNotional() { + return notional; + } + + //----------------------------------------------------------------------- + /** + * Gets the interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + * @return the value of the property, not null + */ + public OvernightRateCalculation getCalculation() { + return calculation; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + * @return the optional value of the property, not null + */ + public Optional getCapSchedule() { + return Optional.ofNullable(capSchedule); + } + + //----------------------------------------------------------------------- + /** + * Gets the floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + * @return the optional value of the property, not null + */ + public Optional getFloorSchedule() { + return Optional.ofNullable(floorSchedule); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloorLeg other = (OvernightInArrearsCapFloorLeg) obj; + return JodaBeanUtils.equal(payReceive, other.payReceive) && + JodaBeanUtils.equal(paymentSchedule, other.paymentSchedule) && + JodaBeanUtils.equal(paymentDateOffset, other.paymentDateOffset) && + JodaBeanUtils.equal(currency, other.currency) && + JodaBeanUtils.equal(notional, other.notional) && + JodaBeanUtils.equal(calculation, other.calculation) && + JodaBeanUtils.equal(capSchedule, other.capSchedule) && + JodaBeanUtils.equal(floorSchedule, other.floorSchedule); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(payReceive); + hash = hash * 31 + JodaBeanUtils.hashCode(paymentSchedule); + hash = hash * 31 + JodaBeanUtils.hashCode(paymentDateOffset); + hash = hash * 31 + JodaBeanUtils.hashCode(currency); + hash = hash * 31 + JodaBeanUtils.hashCode(notional); + hash = hash * 31 + JodaBeanUtils.hashCode(calculation); + hash = hash * 31 + JodaBeanUtils.hashCode(capSchedule); + hash = hash * 31 + JodaBeanUtils.hashCode(floorSchedule); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(288); + buf.append("OvernightInArrearsCapFloorLeg{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("paymentSchedule").append('=').append(JodaBeanUtils.toString(paymentSchedule)).append(',').append(' '); + buf.append("paymentDateOffset").append('=').append(JodaBeanUtils.toString(paymentDateOffset)).append(',').append(' '); + buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); + buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); + buf.append("calculation").append('=').append(JodaBeanUtils.toString(calculation)).append(',').append(' '); + buf.append("capSchedule").append('=').append(JodaBeanUtils.toString(capSchedule)).append(',').append(' '); + buf.append("floorSchedule").append('=').append(JodaBeanUtils.toString(floorSchedule)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorLeg}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code payReceive} property. + */ + private final MetaProperty payReceive = DirectMetaProperty.ofImmutable( + this, "payReceive", OvernightInArrearsCapFloorLeg.class, PayReceive.class); + /** + * The meta-property for the {@code paymentSchedule} property. + */ + private final MetaProperty paymentSchedule = DirectMetaProperty.ofImmutable( + this, "paymentSchedule", OvernightInArrearsCapFloorLeg.class, PeriodicSchedule.class); + /** + * The meta-property for the {@code paymentDateOffset} property. + */ + private final MetaProperty paymentDateOffset = DirectMetaProperty.ofImmutable( + this, "paymentDateOffset", OvernightInArrearsCapFloorLeg.class, DaysAdjustment.class); + /** + * The meta-property for the {@code currency} property. + */ + private final MetaProperty currency = DirectMetaProperty.ofImmutable( + this, "currency", OvernightInArrearsCapFloorLeg.class, Currency.class); + /** + * The meta-property for the {@code notional} property. + */ + private final MetaProperty notional = DirectMetaProperty.ofImmutable( + this, "notional", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-property for the {@code calculation} property. + */ + private final MetaProperty calculation = DirectMetaProperty.ofImmutable( + this, "calculation", OvernightInArrearsCapFloorLeg.class, OvernightRateCalculation.class); + /** + * The meta-property for the {@code capSchedule} property. + */ + private final MetaProperty capSchedule = DirectMetaProperty.ofImmutable( + this, "capSchedule", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-property for the {@code floorSchedule} property. + */ + private final MetaProperty floorSchedule = DirectMetaProperty.ofImmutable( + this, "floorSchedule", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "payReceive", + "paymentSchedule", + "paymentDateOffset", + "currency", + "notional", + "calculation", + "capSchedule", + "floorSchedule"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case -1499086147: // paymentSchedule + return paymentSchedule; + case -716438393: // paymentDateOffset + return paymentDateOffset; + case 575402001: // currency + return currency; + case 1585636160: // notional + return notional; + case -934682935: // calculation + return calculation; + case -596212599: // capSchedule + return capSchedule; + case -1562227005: // floorSchedule + return floorSchedule; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapFloorLeg.Builder builder() { + return new OvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloorLeg.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code payReceive} property. + * @return the meta-property, not null + */ + public MetaProperty payReceive() { + return payReceive; + } + + /** + * The meta-property for the {@code paymentSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty paymentSchedule() { + return paymentSchedule; + } + + /** + * The meta-property for the {@code paymentDateOffset} property. + * @return the meta-property, not null + */ + public MetaProperty paymentDateOffset() { + return paymentDateOffset; + } + + /** + * The meta-property for the {@code currency} property. + * @return the meta-property, not null + */ + public MetaProperty currency() { + return currency; + } + + /** + * The meta-property for the {@code notional} property. + * @return the meta-property, not null + */ + public MetaProperty notional() { + return notional; + } + + /** + * The meta-property for the {@code calculation} property. + * @return the meta-property, not null + */ + public MetaProperty calculation() { + return calculation; + } + + /** + * The meta-property for the {@code capSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty capSchedule() { + return capSchedule; + } + + /** + * The meta-property for the {@code floorSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty floorSchedule() { + return floorSchedule; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return ((OvernightInArrearsCapFloorLeg) bean).getPayReceive(); + case -1499086147: // paymentSchedule + return ((OvernightInArrearsCapFloorLeg) bean).getPaymentSchedule(); + case -716438393: // paymentDateOffset + return ((OvernightInArrearsCapFloorLeg) bean).getPaymentDateOffset(); + case 575402001: // currency + return ((OvernightInArrearsCapFloorLeg) bean).getCurrency(); + case 1585636160: // notional + return ((OvernightInArrearsCapFloorLeg) bean).getNotional(); + case -934682935: // calculation + return ((OvernightInArrearsCapFloorLeg) bean).getCalculation(); + case -596212599: // capSchedule + return ((OvernightInArrearsCapFloorLeg) bean).capSchedule; + case -1562227005: // floorSchedule + return ((OvernightInArrearsCapFloorLeg) bean).floorSchedule; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloorLeg}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private PayReceive payReceive; + private PeriodicSchedule paymentSchedule; + private DaysAdjustment paymentDateOffset; + private Currency currency; + private ValueSchedule notional; + private OvernightRateCalculation calculation; + private ValueSchedule capSchedule; + private ValueSchedule floorSchedule; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapFloorLeg beanToCopy) { + this.payReceive = beanToCopy.getPayReceive(); + this.paymentSchedule = beanToCopy.getPaymentSchedule(); + this.paymentDateOffset = beanToCopy.getPaymentDateOffset(); + this.currency = beanToCopy.getCurrency(); + this.notional = beanToCopy.getNotional(); + this.calculation = beanToCopy.getCalculation(); + this.capSchedule = beanToCopy.capSchedule; + this.floorSchedule = beanToCopy.floorSchedule; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case -1499086147: // paymentSchedule + return paymentSchedule; + case -716438393: // paymentDateOffset + return paymentDateOffset; + case 575402001: // currency + return currency; + case 1585636160: // notional + return notional; + case -934682935: // calculation + return calculation; + case -596212599: // capSchedule + return capSchedule; + case -1562227005: // floorSchedule + return floorSchedule; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + this.payReceive = (PayReceive) newValue; + break; + case -1499086147: // paymentSchedule + this.paymentSchedule = (PeriodicSchedule) newValue; + break; + case -716438393: // paymentDateOffset + this.paymentDateOffset = (DaysAdjustment) newValue; + break; + case 575402001: // currency + this.currency = (Currency) newValue; + break; + case 1585636160: // notional + this.notional = (ValueSchedule) newValue; + break; + case -934682935: // calculation + this.calculation = (OvernightRateCalculation) newValue; + break; + case -596212599: // capSchedule + this.capSchedule = (ValueSchedule) newValue; + break; + case -1562227005: // floorSchedule + this.floorSchedule = (ValueSchedule) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapFloorLeg build() { + return new OvernightInArrearsCapFloorLeg( + payReceive, + paymentSchedule, + paymentDateOffset, + currency, + notional, + calculation, + capSchedule, + floorSchedule); + } + + //----------------------------------------------------------------------- + /** + * Sets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + * @param payReceive the new value, not null + * @return this, for chaining, not null + */ + public Builder payReceive(PayReceive payReceive) { + JodaBeanUtils.notNull(payReceive, "payReceive"); + this.payReceive = payReceive; + return this; + } + + /** + * Sets the periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + * @param paymentSchedule the new value, not null + * @return this, for chaining, not null + */ + public Builder paymentSchedule(PeriodicSchedule paymentSchedule) { + JodaBeanUtils.notNull(paymentSchedule, "paymentSchedule"); + this.paymentSchedule = paymentSchedule; + return this; + } + + /** + * Sets the offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + * @param paymentDateOffset the new value, not null + * @return this, for chaining, not null + */ + public Builder paymentDateOffset(DaysAdjustment paymentDateOffset) { + JodaBeanUtils.notNull(paymentDateOffset, "paymentDateOffset"); + this.paymentDateOffset = paymentDateOffset; + return this; + } + + /** + * Sets the currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + * @param currency the new value, not null + * @return this, for chaining, not null + */ + public Builder currency(Currency currency) { + JodaBeanUtils.notNull(currency, "currency"); + this.currency = currency; + return this; + } + + /** + * Sets the notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + * @param notional the new value, not null + * @return this, for chaining, not null + */ + public Builder notional(ValueSchedule notional) { + JodaBeanUtils.notNull(notional, "notional"); + this.notional = notional; + return this; + } + + /** + * Sets the interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + * @param calculation the new value, not null + * @return this, for chaining, not null + */ + public Builder calculation(OvernightRateCalculation calculation) { + JodaBeanUtils.notNull(calculation, "calculation"); + this.calculation = calculation; + return this; + } + + /** + * Sets the cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + * @param capSchedule the new value + * @return this, for chaining, not null + */ + public Builder capSchedule(ValueSchedule capSchedule) { + this.capSchedule = capSchedule; + return this; + } + + /** + * Sets the floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + * @param floorSchedule the new value + * @return this, for chaining, not null + */ + public Builder floorSchedule(ValueSchedule floorSchedule) { + this.floorSchedule = floorSchedule; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(288); + buf.append("OvernightInArrearsCapFloorLeg.Builder{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("paymentSchedule").append('=').append(JodaBeanUtils.toString(paymentSchedule)).append(',').append(' '); + buf.append("paymentDateOffset").append('=').append(JodaBeanUtils.toString(paymentDateOffset)).append(',').append(' '); + buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); + buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); + buf.append("calculation").append('=').append(JodaBeanUtils.toString(calculation)).append(',').append(' '); + buf.append("capSchedule").append('=').append(JodaBeanUtils.toString(capSchedule)).append(',').append(' '); + buf.append("floorSchedule").append('=').append(JodaBeanUtils.toString(floorSchedule)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java new file mode 100644 index 0000000000..e9c195814a --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.product.PortfolioItemInfo; +import com.opengamma.strata.product.PortfolioItemSummary; +import com.opengamma.strata.product.ProductTrade; +import com.opengamma.strata.product.ProductType; +import com.opengamma.strata.product.ResolvableTrade; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.common.SummarizerUtils; + +/** + * A trade in an overnight rate in arrears cap/floor. + *

+ * An Over-The-Counter (OTC) trade in a {@link OvernightInArrearsCapFloor}. + *

+ * An overnight rate in arrears cap/floor is a financial instrument that provides a set of call/put options on + * successive compounded overnight index rates. + */ +@BeanDefinition +public final class OvernightInArrearsCapFloorTrade + implements ProductTrade, ResolvableTrade, ImmutableBean, Serializable { + + /** + * The additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final TradeInfo info; + /** + * The cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final OvernightInArrearsCapFloor product; + /** + * The optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + */ + @PropertyDefinition(get = "optional") + private final AdjustablePayment premium; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.info = TradeInfo.empty(); + } + + //------------------------------------------------------------------------- + @Override + public OvernightInArrearsCapFloorTrade withInfo(PortfolioItemInfo info) { + return new OvernightInArrearsCapFloorTrade(TradeInfo.from(info), product, premium); + } + + //------------------------------------------------------------------------- + @Override + public PortfolioItemSummary summarize() { + // 5Y USD 2mm Rec Compounded USD-SOFR Cap 1% / Pay Premium : 21Jan17-21Jan22 + StringBuilder buf = new StringBuilder(96); + OvernightInArrearsCapFloorLeg mainLeg = product.getCapFloorLeg(); + buf.append( + SummarizerUtils.datePeriod( + mainLeg.getStartDate().getUnadjusted(), + mainLeg.getEndDate().getUnadjusted())); + buf.append(' '); + buf.append(SummarizerUtils.amount(mainLeg.getCurrency(), mainLeg.getNotional().getInitialValue())); + buf.append(' '); + if (mainLeg.getPayReceive().isReceive()) { + buf.append("Rec "); + summarizeMainLeg(mainLeg, buf); + buf.append(getPremium().isPresent() ? + " / Pay Premium" : + (product.getPayLeg().isPresent() ? " / Pay Periodic" : "")); + } else { + buf.append(getPremium().isPresent() ? + "Rec Premium / Pay " : + (product.getPayLeg().isPresent() ? "Rec Periodic / Pay " : "")); + summarizeMainLeg(mainLeg, buf); + } + buf.append(" : "); + buf.append(SummarizerUtils.dateRange(mainLeg.getStartDate().getUnadjusted(), mainLeg.getEndDate().getUnadjusted())); + return SummarizerUtils.summary( + this, + ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR, + buf.toString(), + mainLeg.getCurrency()); + } + + // summarize the main leg + private void summarizeMainLeg(OvernightInArrearsCapFloorLeg mainLeg, StringBuilder buf) { + buf.append(mainLeg.getCalculation().getAccrualMethod()); + buf.append(' '); + buf.append(mainLeg.getIndex()); + buf.append(' '); + if (mainLeg.getCapSchedule().isPresent()) { + buf.append("Cap "); + buf.append(SummarizerUtils.percent(mainLeg.getCapSchedule().get().getInitialValue())); + } + if (mainLeg.getFloorSchedule().isPresent()) { + buf.append("Floor "); + buf.append(SummarizerUtils.percent(mainLeg.getFloorSchedule().get().getInitialValue())); + } + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade resolve(ReferenceData refData) { + return ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(info) + .product(product.resolve(refData)) + .premium(premium != null ? premium.resolve(refData) : null) + .build(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorTrade}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloorTrade.Meta meta() { + return OvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloorTrade.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapFloorTrade.Builder builder() { + return new OvernightInArrearsCapFloorTrade.Builder(); + } + + private OvernightInArrearsCapFloorTrade( + TradeInfo info, + OvernightInArrearsCapFloor product, + AdjustablePayment premium) { + JodaBeanUtils.notNull(info, "info"); + JodaBeanUtils.notNull(product, "product"); + this.info = info; + this.product = product; + this.premium = premium; + } + + @Override + public OvernightInArrearsCapFloorTrade.Meta metaBean() { + return OvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @return the value of the property, not null + */ + @Override + public TradeInfo getInfo() { + return info; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + * @return the value of the property, not null + */ + @Override + public OvernightInArrearsCapFloor getProduct() { + return product; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @return the optional value of the property, not null + */ + public Optional getPremium() { + return Optional.ofNullable(premium); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloorTrade other = (OvernightInArrearsCapFloorTrade) obj; + return JodaBeanUtils.equal(info, other.info) && + JodaBeanUtils.equal(product, other.product) && + JodaBeanUtils.equal(premium, other.premium); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(info); + hash = hash * 31 + JodaBeanUtils.hashCode(product); + hash = hash * 31 + JodaBeanUtils.hashCode(premium); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("OvernightInArrearsCapFloorTrade{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorTrade}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code info} property. + */ + private final MetaProperty info = DirectMetaProperty.ofImmutable( + this, "info", OvernightInArrearsCapFloorTrade.class, TradeInfo.class); + /** + * The meta-property for the {@code product} property. + */ + private final MetaProperty product = DirectMetaProperty.ofImmutable( + this, "product", OvernightInArrearsCapFloorTrade.class, OvernightInArrearsCapFloor.class); + /** + * The meta-property for the {@code premium} property. + */ + private final MetaProperty premium = DirectMetaProperty.ofImmutable( + this, "premium", OvernightInArrearsCapFloorTrade.class, AdjustablePayment.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "info", + "product", + "premium"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapFloorTrade.Builder builder() { + return new OvernightInArrearsCapFloorTrade.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloorTrade.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code info} property. + * @return the meta-property, not null + */ + public MetaProperty info() { + return info; + } + + /** + * The meta-property for the {@code product} property. + * @return the meta-property, not null + */ + public MetaProperty product() { + return product; + } + + /** + * The meta-property for the {@code premium} property. + * @return the meta-property, not null + */ + public MetaProperty premium() { + return premium; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 3237038: // info + return ((OvernightInArrearsCapFloorTrade) bean).getInfo(); + case -309474065: // product + return ((OvernightInArrearsCapFloorTrade) bean).getProduct(); + case -318452137: // premium + return ((OvernightInArrearsCapFloorTrade) bean).premium; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloorTrade}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private TradeInfo info; + private OvernightInArrearsCapFloor product; + private AdjustablePayment premium; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapFloorTrade beanToCopy) { + this.info = beanToCopy.getInfo(); + this.product = beanToCopy.getProduct(); + this.premium = beanToCopy.premium; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 3237038: // info + this.info = (TradeInfo) newValue; + break; + case -309474065: // product + this.product = (OvernightInArrearsCapFloor) newValue; + break; + case -318452137: // premium + this.premium = (AdjustablePayment) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapFloorTrade build() { + return new OvernightInArrearsCapFloorTrade( + info, + product, + premium); + } + + //----------------------------------------------------------------------- + /** + * Sets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @param info the new value, not null + * @return this, for chaining, not null + */ + public Builder info(TradeInfo info) { + JodaBeanUtils.notNull(info, "info"); + this.info = info; + return this; + } + + /** + * Sets the cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + * @param product the new value, not null + * @return this, for chaining, not null + */ + public Builder product(OvernightInArrearsCapFloor product) { + JodaBeanUtils.notNull(product, "product"); + this.product = product; + return this; + } + + /** + * Sets the optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @param premium the new value + * @return this, for chaining, not null + */ + public Builder premium(AdjustablePayment premium) { + this.premium = premium; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("OvernightInArrearsCapFloorTrade.Builder{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java index 66ed183438..2d33087b80 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java @@ -35,7 +35,7 @@ import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; /** - * A period over which an caplet/floorlet on overnight composition in-arrears is paid. + * A period over which a caplet/floorlet on overnight composition in-arrears is paid. *

* The payoff depend on the level of the compounded rate over the period. The option is * of Asian type with the averaging mechanism given by the composition. diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java new file mode 100644 index 0000000000..314cebe2c8 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.BeanBuilder; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; +import org.joda.beans.impl.direct.DirectPrivateBeanBuilder; + +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.Index; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.ResolvedProduct; +import com.opengamma.strata.product.swap.ResolvedSwapLeg; + +/** + * An overnight rate in arrears cap/floor, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloor} and is an input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloor} + * from a {@code OvernightInArrearsCapFloor} using {@link OvernightInArrearsCapFloor#resolve(ReferenceData)}. + *

+ * A {@code ResolvedOvernightInArrearsCapFloor} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition(builderScope = "private") +public final class ResolvedOvernightInArrearsCapFloor + implements ResolvedProduct, ImmutableBean, Serializable { + + /** + * The overnight rate in arrears cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are overnight rate in arrears caplets or floorlets. + */ + @PropertyDefinition(validate = "notNull") + private final ResolvedOvernightInArrearsCapFloorLeg capFloorLeg; + /** + * The optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. Instead the premium is paid upfront. + */ + @PropertyDefinition(get = "optional") + private final ResolvedSwapLeg payLeg; + /** + * The set of currencies. + */ + private final transient ImmutableSet currencies; // not a property, derived and cached from input data + /** + * The set of indices. + */ + private final transient ImmutableSet indices; // not a property, derived and cached from input data + + //------------------------------------------------------------------------- + /** + * Obtains an instance from a cap/floor leg with no pay leg. + *

+ * The pay leg is absent in the resulting cap/floor. + * + * @param capFloorLeg the cap/floor leg + * @return the cap/floor + */ + public static ResolvedOvernightInArrearsCapFloor of(ResolvedOvernightInArrearsCapFloorLeg capFloorLeg) { + ArgChecker.notNull(capFloorLeg, "capFloorLeg"); + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, null); + } + + /** + * Obtains an instance from a cap/floor leg and a pay leg. + * + * @param capFloorLeg the cap/floor leg + * @param payLeg the pay leg + * @return the cap/floor + */ + public static ResolvedOvernightInArrearsCapFloor of( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ArgChecker.notNull(capFloorLeg, "capFloorLeg"); + ArgChecker.notNull(payLeg, "payLeg"); + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + @ImmutableConstructor + private ResolvedOvernightInArrearsCapFloor( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + JodaBeanUtils.notNull(capFloorLeg, "capFloorLeg"); + if (payLeg != null) { + ArgChecker.isFalse( + payLeg.getPayReceive().equals(capFloorLeg.getPayReceive()), + "Legs must have different Pay/Receive flag, but both were {}", payLeg.getPayReceive()); + } + this.capFloorLeg = capFloorLeg; + this.payLeg = payLeg; + this.currencies = buildCurrencies(capFloorLeg, payLeg); + this.indices = buildIndices(capFloorLeg, payLeg); + } + + // collect the set of currencies + private static ImmutableSet buildCurrencies( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCurrency()); + if (payLeg != null) { + builder.add(payLeg.getCurrency()); + } + return builder.build(); + } + + // collect the set of indices + private static ImmutableSet buildIndices( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getIndex()); + if (payLeg != null) { + payLeg.collectIndices(builder); + } + return builder.build(); + } + + // ensure standard constructor is invoked + private Object readResolve() { + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + /** + * Returns the set of payment currencies referred to by the cap/floor. + *

+ * This returns the complete set of payment currencies for the cap/floor. + * This will typically return one currency, but could return two. + * + * @return the set of payment currencies referred to by this swap + */ + public ImmutableSet allPaymentCurrencies() { + return currencies; + } + + /** + * Returns the set of indices referred to by the cap/floor. + *

+ * A cap/floor will typically refer to one index, such as 'GBP-SONIA'. + * Calling this method will return the complete list of indices. + * + * @return the set of indices referred to by this cap/floor + */ + public ImmutableSet allIndices() { + return indices; + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloor}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloor.Meta meta() { + return ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + @Override + public ResolvedOvernightInArrearsCapFloor.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the overnight rate in arrears cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are overnight rate in arrears caplets or floorlets. + * @return the value of the property, not null + */ + public ResolvedOvernightInArrearsCapFloorLeg getCapFloorLeg() { + return capFloorLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. Instead the premium is paid upfront. + * @return the optional value of the property, not null + */ + public Optional getPayLeg() { + return Optional.ofNullable(payLeg); + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloor other = (ResolvedOvernightInArrearsCapFloor) obj; + return JodaBeanUtils.equal(capFloorLeg, other.capFloorLeg) && + JodaBeanUtils.equal(payLeg, other.payLeg); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(capFloorLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(payLeg); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloor{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloor}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code capFloorLeg} property. + */ + private final MetaProperty capFloorLeg = DirectMetaProperty.ofImmutable( + this, "capFloorLeg", ResolvedOvernightInArrearsCapFloor.class, ResolvedOvernightInArrearsCapFloorLeg.class); + /** + * The meta-property for the {@code payLeg} property. + */ + private final MetaProperty payLeg = DirectMetaProperty.ofImmutable( + this, "payLeg", ResolvedOvernightInArrearsCapFloor.class, ResolvedSwapLeg.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "capFloorLeg", + "payLeg"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public BeanBuilder builder() { + return new ResolvedOvernightInArrearsCapFloor.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloor.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code capFloorLeg} property. + * @return the meta-property, not null + */ + public MetaProperty capFloorLeg() { + return capFloorLeg; + } + + /** + * The meta-property for the {@code payLeg} property. + * @return the meta-property, not null + */ + public MetaProperty payLeg() { + return payLeg; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return ((ResolvedOvernightInArrearsCapFloor) bean).getCapFloorLeg(); + case -995239866: // payLeg + return ((ResolvedOvernightInArrearsCapFloor) bean).payLeg; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloor}. + */ + private static final class Builder extends DirectPrivateBeanBuilder { + + private ResolvedOvernightInArrearsCapFloorLeg capFloorLeg; + private ResolvedSwapLeg payLeg; + + /** + * Restricted constructor. + */ + private Builder() { + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + this.capFloorLeg = (ResolvedOvernightInArrearsCapFloorLeg) newValue; + break; + case -995239866: // payLeg + this.payLeg = (ResolvedSwapLeg) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloor build() { + return new ResolvedOvernightInArrearsCapFloor( + capFloorLeg, + payLeg); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloor.Builder{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java new file mode 100644 index 0000000000..fe94ad2f8f --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableList; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.common.PayReceive; + +/** + * A cap/floor leg of an overnight rate in arrears cap/floor product, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloorLeg} and is an input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloorLeg} + * from a {@code OvernightInArrearsCapFloorLeg} using {@link OvernightInArrearsCapFloorLeg#resolve(ReferenceData)}. + *

+ * This defines a single leg for an overnight rate in arrears cap/floor product and is formed from a number of periods. + * Each period may be a caplet or floorlet. + * The cap/floor instruments are defined as a set of call/put options on successive compounded overnight index rates. + *

+ * A {@code ResolvedOvernightInArrearsCapFloorLeg} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition +public final class ResolvedOvernightInArrearsCapFloorLeg + implements ImmutableBean, Serializable { + + /** + * Whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + */ + @PropertyDefinition(validate = "notNull") + private final PayReceive payReceive; + /** + * The periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + */ + @PropertyDefinition(validate = "notEmpty") + private final ImmutableList capletFloorletPeriods; + + //------------------------------------------------------------------------- + @ImmutableConstructor + private ResolvedOvernightInArrearsCapFloorLeg( + PayReceive payReceive, + List capletFloorletPeriods) { + + this.payReceive = ArgChecker.notNull(payReceive, "payReceive"); + this.capletFloorletPeriods = ImmutableList.copyOf(capletFloorletPeriods); + Set currencies = + this.capletFloorletPeriods.stream().map(OvernightInArrearsCapletFloorletPeriod::getCurrency).collect(Collectors.toSet()); + ArgChecker.isTrue(currencies.size() == 1, "Leg must have a single currency, found: " + currencies); + Set indices = + this.capletFloorletPeriods.stream().map(OvernightInArrearsCapletFloorletPeriod::getIndex).collect(Collectors.toSet()); + ArgChecker.isTrue(indices.size() == 1, "Leg must have a single overnight index: " + indices); + } + + //------------------------------------------------------------------------- + /** + * Gets the accrual start date of the leg. + *

+ * This is the first accrual date in the leg, often known as the effective date. + * This date has typically been adjusted to be a valid business day. + * + * @return the start date of the leg + */ + public LocalDate getStartDate() { + return capletFloorletPeriods.get(0).getStartDate(); + } + + /** + * Gets the accrual end date of the leg. + *

+ * This is the last accrual date in the leg, often known as the termination date. + * This date has typically been adjusted to be a valid business day. + * + * @return the end date of the leg + */ + public LocalDate getEndDate() { + return capletFloorletPeriods.get(capletFloorletPeriods.size() - 1).getEndDate(); + } + + /** + * Gets the final caplet/floorlet period. + * + * @return the final period + */ + public OvernightInArrearsCapletFloorletPeriod getFinalPeriod() { + return capletFloorletPeriods.get(capletFloorletPeriods.size() - 1); + } + + /** + * Gets the currency of the leg. + *

+ * All periods in the leg will have this currency. + * + * @return the currency + */ + public Currency getCurrency() { + return capletFloorletPeriods.get(0).getCurrency(); + } + + /** + * Gets the overnight index of the leg. + *

+ * All periods in the leg will have this index. + * + * @return the index + */ + public OvernightIndex getIndex() { + return capletFloorletPeriods.get(0).getIndex(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorLeg}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloorLeg.Meta meta() { + return ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static ResolvedOvernightInArrearsCapFloorLeg.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + * @return the value of the property, not null + */ + public PayReceive getPayReceive() { + return payReceive; + } + + //----------------------------------------------------------------------- + /** + * Gets the periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + * @return the value of the property, not empty + */ + public ImmutableList getCapletFloorletPeriods() { + return capletFloorletPeriods; + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloorLeg other = (ResolvedOvernightInArrearsCapFloorLeg) obj; + return JodaBeanUtils.equal(payReceive, other.payReceive) && + JodaBeanUtils.equal(capletFloorletPeriods, other.capletFloorletPeriods); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(payReceive); + hash = hash * 31 + JodaBeanUtils.hashCode(capletFloorletPeriods); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloorLeg{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("capletFloorletPeriods").append('=').append(JodaBeanUtils.toString(capletFloorletPeriods)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorLeg}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code payReceive} property. + */ + private final MetaProperty payReceive = DirectMetaProperty.ofImmutable( + this, "payReceive", ResolvedOvernightInArrearsCapFloorLeg.class, PayReceive.class); + /** + * The meta-property for the {@code capletFloorletPeriods} property. + */ + @SuppressWarnings({"unchecked", "rawtypes" }) + private final MetaProperty> capletFloorletPeriods = DirectMetaProperty.ofImmutable( + this, "capletFloorletPeriods", ResolvedOvernightInArrearsCapFloorLeg.class, (Class) ImmutableList.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "payReceive", + "capletFloorletPeriods"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case 1504863482: // capletFloorletPeriods + return capletFloorletPeriods; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloorLeg.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code payReceive} property. + * @return the meta-property, not null + */ + public MetaProperty payReceive() { + return payReceive; + } + + /** + * The meta-property for the {@code capletFloorletPeriods} property. + * @return the meta-property, not null + */ + public MetaProperty> capletFloorletPeriods() { + return capletFloorletPeriods; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return ((ResolvedOvernightInArrearsCapFloorLeg) bean).getPayReceive(); + case 1504863482: // capletFloorletPeriods + return ((ResolvedOvernightInArrearsCapFloorLeg) bean).getCapletFloorletPeriods(); + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloorLeg}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private PayReceive payReceive; + private List capletFloorletPeriods = ImmutableList.of(); + + /** + * Restricted constructor. + */ + private Builder() { + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(ResolvedOvernightInArrearsCapFloorLeg beanToCopy) { + this.payReceive = beanToCopy.getPayReceive(); + this.capletFloorletPeriods = beanToCopy.getCapletFloorletPeriods(); + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case 1504863482: // capletFloorletPeriods + return capletFloorletPeriods; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @SuppressWarnings("unchecked") + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + this.payReceive = (PayReceive) newValue; + break; + case 1504863482: // capletFloorletPeriods + this.capletFloorletPeriods = (List) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg build() { + return new ResolvedOvernightInArrearsCapFloorLeg( + payReceive, + capletFloorletPeriods); + } + + //----------------------------------------------------------------------- + /** + * Sets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + * @param payReceive the new value, not null + * @return this, for chaining, not null + */ + public Builder payReceive(PayReceive payReceive) { + JodaBeanUtils.notNull(payReceive, "payReceive"); + this.payReceive = payReceive; + return this; + } + + /** + * Sets the periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + * @param capletFloorletPeriods the new value, not empty + * @return this, for chaining, not null + */ + public Builder capletFloorletPeriods(List capletFloorletPeriods) { + JodaBeanUtils.notEmpty(capletFloorletPeriods, "capletFloorletPeriods"); + this.capletFloorletPeriods = capletFloorletPeriods; + return this; + } + + /** + * Sets the {@code capletFloorletPeriods} property in the builder + * from an array of objects. + * @param capletFloorletPeriods the new value, not empty + * @return this, for chaining, not null + */ + public Builder capletFloorletPeriods(OvernightInArrearsCapletFloorletPeriod... capletFloorletPeriods) { + return capletFloorletPeriods(ImmutableList.copyOf(capletFloorletPeriods)); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloorLeg.Builder{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("capletFloorletPeriods").append('=').append(JodaBeanUtils.toString(capletFloorletPeriods)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java new file mode 100644 index 0000000000..25c27db7e7 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.product.ResolvedTrade; +import com.opengamma.strata.product.TradeInfo; + +/** + * A trade in an overnight rate in arrears cap/floor, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloorTrade} and is the primary input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloorTrade} from + * a {@code OvernightInArrearsCapFloorTrade} using {@link OvernightInArrearsCapFloorTrade#resolve(ReferenceData)}. + *

+ * A {@code OvernightInArrearsCapFloorTrade} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition +public final class ResolvedOvernightInArrearsCapFloorTrade + implements ResolvedTrade, ImmutableBean, Serializable { + + /** + * The additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final TradeInfo info; + /** + * The resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final ResolvedOvernightInArrearsCapFloor product; + /** + * The optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + */ + @PropertyDefinition(get = "optional") + private final Payment premium; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.info = TradeInfo.empty(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorTrade}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloorTrade.Meta meta() { + return ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static ResolvedOvernightInArrearsCapFloorTrade.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorTrade.Builder(); + } + + private ResolvedOvernightInArrearsCapFloorTrade( + TradeInfo info, + ResolvedOvernightInArrearsCapFloor product, + Payment premium) { + JodaBeanUtils.notNull(info, "info"); + JodaBeanUtils.notNull(product, "product"); + this.info = info; + this.product = product; + this.premium = premium; + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @return the value of the property, not null + */ + @Override + public TradeInfo getInfo() { + return info; + } + + //----------------------------------------------------------------------- + /** + * Gets the resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + * @return the value of the property, not null + */ + @Override + public ResolvedOvernightInArrearsCapFloor getProduct() { + return product; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @return the optional value of the property, not null + */ + public Optional getPremium() { + return Optional.ofNullable(premium); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloorTrade other = (ResolvedOvernightInArrearsCapFloorTrade) obj; + return JodaBeanUtils.equal(info, other.info) && + JodaBeanUtils.equal(product, other.product) && + JodaBeanUtils.equal(premium, other.premium); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(info); + hash = hash * 31 + JodaBeanUtils.hashCode(product); + hash = hash * 31 + JodaBeanUtils.hashCode(premium); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("ResolvedOvernightInArrearsCapFloorTrade{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorTrade}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code info} property. + */ + private final MetaProperty info = DirectMetaProperty.ofImmutable( + this, "info", ResolvedOvernightInArrearsCapFloorTrade.class, TradeInfo.class); + /** + * The meta-property for the {@code product} property. + */ + private final MetaProperty product = DirectMetaProperty.ofImmutable( + this, "product", ResolvedOvernightInArrearsCapFloorTrade.class, ResolvedOvernightInArrearsCapFloor.class); + /** + * The meta-property for the {@code premium} property. + */ + private final MetaProperty premium = DirectMetaProperty.ofImmutable( + this, "premium", ResolvedOvernightInArrearsCapFloorTrade.class, Payment.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "info", + "product", + "premium"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorTrade.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloorTrade.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code info} property. + * @return the meta-property, not null + */ + public MetaProperty info() { + return info; + } + + /** + * The meta-property for the {@code product} property. + * @return the meta-property, not null + */ + public MetaProperty product() { + return product; + } + + /** + * The meta-property for the {@code premium} property. + * @return the meta-property, not null + */ + public MetaProperty premium() { + return premium; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 3237038: // info + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).getInfo(); + case -309474065: // product + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).getProduct(); + case -318452137: // premium + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).premium; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloorTrade}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private TradeInfo info; + private ResolvedOvernightInArrearsCapFloor product; + private Payment premium; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(ResolvedOvernightInArrearsCapFloorTrade beanToCopy) { + this.info = beanToCopy.getInfo(); + this.product = beanToCopy.getProduct(); + this.premium = beanToCopy.premium; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 3237038: // info + this.info = (TradeInfo) newValue; + break; + case -309474065: // product + this.product = (ResolvedOvernightInArrearsCapFloor) newValue; + break; + case -318452137: // premium + this.premium = (Payment) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade build() { + return new ResolvedOvernightInArrearsCapFloorTrade( + info, + product, + premium); + } + + //----------------------------------------------------------------------- + /** + * Sets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @param info the new value, not null + * @return this, for chaining, not null + */ + public Builder info(TradeInfo info) { + JodaBeanUtils.notNull(info, "info"); + this.info = info; + return this; + } + + /** + * Sets the resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + * @param product the new value, not null + * @return this, for chaining, not null + */ + public Builder product(ResolvedOvernightInArrearsCapFloor product) { + JodaBeanUtils.notNull(product, "product"); + this.product = product; + return this; + } + + /** + * Sets the optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @param premium the new value + * @return this, for chaining, not null + */ + public Builder premium(Payment premium) { + this.premium = premium; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("ResolvedOvernightInArrearsCapFloorTrade.Builder{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java new file mode 100644 index 0000000000..84f39867f6 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_EONIA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +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; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.AdjustableDate; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.index.OvernightIndices; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +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.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.OvernightAccrualMethod; +import com.opengamma.strata.product.swap.OvernightRateCalculation; + +/** + * Test {@link OvernightInArrearsCapFloorLeg}. + */ +public class OvernightInArrearsCapFloorLegTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2012, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .stubConvention(StubConvention.NONE) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + + private static final double[] NOTIONALS = new double[] {1.0e6, 1.2e6, 0.8e6, 1.0e6}; + private static final double[] STRIKES = new double[] {0.03, 0.0275, 0.02, 0.0345}; + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final List FLOOR_STEPS = new ArrayList(); + private static final List NOTIONAL_STEPS = new ArrayList(); + static { + FLOOR_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(STRIKES[1]))); + FLOOR_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(STRIKES[2]))); + FLOOR_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(STRIKES[3]))); + NOTIONAL_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(NOTIONALS[1]))); + NOTIONAL_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(NOTIONALS[2]))); + NOTIONAL_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(NOTIONALS[3]))); + } + private static final ValueSchedule FLOOR = ValueSchedule.of(STRIKES[0], FLOOR_STEPS); + private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONALS[0], NOTIONAL_STEPS); + + @Test + public void test_builder_full() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .currency(GBP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build(); + assertThat(test.getCalculation()).isEqualTo(RATE_CALCULATION); + assertThat(test.getCapSchedule().get()).isEqualTo(CAP); + assertThat(test.getFloorSchedule()).isNotPresent(); + assertThat(test.getCurrency()).isEqualTo(GBP); + assertThat(test.getNotional()).isEqualTo(NOTIONAL); + assertThat(test.getPaymentDateOffset()).isEqualTo(PAYMENT_OFFSET); + assertThat(test.getPaymentSchedule()).isEqualTo(SCHEDULE); + assertThat(test.getPayReceive()).isEqualTo(PAY); + assertThat(test.getStartDate()).isEqualTo(AdjustableDate.of(START, BUSS_ADJ)); + assertThat(test.getEndDate()).isEqualTo(AdjustableDate.of(END, BUSS_ADJ)); + assertThat(test.getIndex()).isEqualTo(EUR_ESTR); + } + + @Test + public void test_builder_min() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + assertThat(test.getCalculation()).isEqualTo(RATE_CALCULATION); + assertThat(test.getCapSchedule()).isNotPresent(); + assertThat(test.getFloorSchedule().get()).isEqualTo(FLOOR); + assertThat(test.getCurrency()).isEqualTo(EUR); + assertThat(test.getNotional()).isEqualTo(NOTIONAL); + assertThat(test.getPaymentDateOffset()).isEqualTo(DaysAdjustment.NONE); + assertThat(test.getPaymentSchedule()).isEqualTo(SCHEDULE); + assertThat(test.getPayReceive()).isEqualTo(RECEIVE); + assertThat(test.getStartDate()).isEqualTo(AdjustableDate.of(START, BUSS_ADJ)); + assertThat(test.getEndDate()).isEqualTo(AdjustableDate.of(END, BUSS_ADJ)); + } + + @Test + public void test_builder_fail() { + // cap and floor present + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build()); + // cap and floor missing + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .notional(NOTIONAL) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build()) + .payReceive(RECEIVE) + .build()); + // stub type + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .currency(GBP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .stubConvention(StubConvention.SHORT_FINAL) + .build()) + .payReceive(PAY) + .build()); + // accrual method not compounded + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(OvernightRateCalculation.builder() + .index(EUR_ESTR) + .accrualMethod(OvernightAccrualMethod.AVERAGED_DAILY) + .build()) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build()); + } + + @Test + public void test_resolve_cap() { + OvernightRateCalculation rateCalc = OvernightRateCalculation.of(EUR_EONIA); + OvernightInArrearsCapFloorLeg base = OvernightInArrearsCapFloorLeg.builder() + .calculation(rateCalc) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + LocalDate[] unadjustedDates = new LocalDate[]{ + START, START.plusMonths(3), + START.plusMonths(6), + START.plusMonths(9), + START.plusMonths(12)}; + OvernightInArrearsCapletFloorletPeriod[] periods = new OvernightInArrearsCapletFloorletPeriod[4]; + for (int i = 0; i < 4; ++i) { + LocalDate start = BUSS_ADJ.adjust(unadjustedDates[i], REF_DATA); + LocalDate end = BUSS_ADJ.adjust(unadjustedDates[i + 1], REF_DATA); + double yearFraction = rateCalc.getDayCount().relativeYearFraction(start, end); + periods[i] = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(CAP.getInitialValue()) + .currency(EUR) + .startDate(start) + .endDate(end) + .unadjustedStartDate(unadjustedDates[i]) + .unadjustedEndDate(unadjustedDates[i + 1]) + .paymentDate(PAYMENT_OFFSET.adjust(end, REF_DATA)) + .notional(NOTIONALS[i]) + .overnightRate(OvernightCompoundedRateComputation.of(EUR_EONIA, start, end, REF_DATA)) + .yearFraction(yearFraction) + .build(); + } + ResolvedOvernightInArrearsCapFloorLeg expected = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periods) + .payReceive(RECEIVE) + .build(); + ResolvedOvernightInArrearsCapFloorLeg computed = base.resolve(REF_DATA); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_resolve_floor() { + OvernightInArrearsCapFloorLeg base = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .currency(EUR) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build(); + LocalDate[] unadjustedDates = new LocalDate[] { + START, + START.plusMonths(3), + START.plusMonths(6), + START.plusMonths(9), + START.plusMonths(12)}; + OvernightInArrearsCapletFloorletPeriod[] periods = new OvernightInArrearsCapletFloorletPeriod[4]; + for (int i = 0; i < 4; ++i) { + LocalDate start = BUSS_ADJ.adjust(unadjustedDates[i], REF_DATA); + LocalDate end = BUSS_ADJ.adjust(unadjustedDates[i + 1], REF_DATA); + double yearFraction = RATE_CALCULATION.getDayCount().relativeYearFraction(start, end); + periods[i] = OvernightInArrearsCapletFloorletPeriod.builder() + .floorlet(STRIKES[i]) + .currency(EUR) + .startDate(start) + .endDate(end) + .unadjustedStartDate(unadjustedDates[i]) + .unadjustedEndDate(unadjustedDates[i + 1]) + .paymentDate(PAYMENT_OFFSET.adjust(end, REF_DATA)) + .notional(-NOTIONALS[i]) + .overnightRate(OvernightCompoundedRateComputation.of(EUR_ESTR, start, end, REF_DATA)) + .yearFraction(yearFraction) + .build(); + } + ResolvedOvernightInArrearsCapFloorLeg expected = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periods) + .payReceive(PAY) + .build(); + ResolvedOvernightInArrearsCapFloorLeg computed = base.resolve(REF_DATA); + assertThat(computed).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloorLeg test1 = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + coverImmutableBean(test1); + OvernightInArrearsCapFloorLeg test2 = OvernightInArrearsCapFloorLeg.builder() + .calculation(OvernightRateCalculation.of(OvernightIndices.GBP_SONIA)) + .capSchedule(CAP) + .notional(ValueSchedule.of(1000)) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(Frequency.P6M) + .businessDayAdjustment(BUSS_ADJ) + .build()) + .payReceive(PAY) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java new file mode 100644 index 0000000000..4742548481 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.date.DayCounts.ACT_360; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.basics.index.OvernightIndices.GBP_SONIA; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +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; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.product.swap.FixedRateCalculation; +import com.opengamma.strata.product.swap.NotionalSchedule; +import com.opengamma.strata.product.swap.OvernightRateCalculation; +import com.opengamma.strata.product.swap.PaymentSchedule; +import com.opengamma.strata.product.swap.RateCalculationSwapLeg; +import com.opengamma.strata.product.swap.SwapLeg; + +/** + * Test {@link OvernightInArrearsCapFloor}. + */ +public class OvernightInArrearsCapFloorTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2016, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final ValueSchedule NOTIONAL = ValueSchedule.of(1.0e6); + private static final OvernightInArrearsCapFloorLeg CAPFLOOR_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final SwapLeg PAY_LEG = RateCalculationSwapLeg.builder() + .payReceive(PAY) + .accrualSchedule(SCHEDULE) + .calculation( + FixedRateCalculation.of(0.001, ACT_360)) + .paymentSchedule( + PaymentSchedule.builder() + .paymentFrequency(FREQUENCY) + .paymentDateOffset(DaysAdjustment.NONE) + .build()) + .notionalSchedule( + NotionalSchedule.of(EUR, NOTIONAL)) + .build(); + private static final SwapLeg PAY_LEG_XCCY = RateCalculationSwapLeg.builder() + .payReceive(PAY) + .accrualSchedule(SCHEDULE) + .calculation( + OvernightRateCalculation.of(GBP_SONIA)) + .paymentSchedule( + PaymentSchedule.builder() + .paymentFrequency(FREQUENCY) + .paymentDateOffset(DaysAdjustment.NONE) + .build()) + .notionalSchedule( + NotionalSchedule.of(GBP, NOTIONAL)) + .build(); + + @Test + public void test_of_oneLeg() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg()).isNotPresent(); + assertThat(test.isCrossCurrency()).isFalse(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG); + assertThat(test.isCrossCurrency()).isFalse(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs_xccy() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG_XCCY); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG_XCCY); + assertThat(test.isCrossCurrency()).isTrue(); + assertThat(test.allPaymentCurrencies()).containsOnly(GBP, EUR); + assertThat(test.allCurrencies()).containsOnly(GBP, EUR); + assertThat(test.allIndices()).containsOnly(GBP_SONIA, EUR_ESTR); + } + + @Test + public void test_resolve_oneLeg() { + OvernightInArrearsCapFloor base = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + ResolvedOvernightInArrearsCapFloor test = base.resolve(REF_DATA); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG.resolve(REF_DATA)); + assertThat(test.getPayLeg()).isNotPresent(); + } + + @Test + public void test_resolve_twoLegs() { + OvernightInArrearsCapFloor base = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + ResolvedOvernightInArrearsCapFloor test = base.resolve(REF_DATA); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG.resolve(REF_DATA)); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG.resolve(REF_DATA)); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloor test1 = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + coverImmutableBean(test1); + OvernightInArrearsCapFloorLeg capFloor = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + OvernightInArrearsCapFloor test2 = OvernightInArrearsCapFloor.of(capFloor, PAY_LEG); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java new file mode 100644 index 0000000000..50154a3081 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +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; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.product.PortfolioItemSummary; +import com.opengamma.strata.product.PortfolioItemType; +import com.opengamma.strata.product.ProductType; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.swap.OvernightRateCalculation; + +/** + * Test {@link OvernightInArrearsCapFloorTrade}. + */ +public class OvernightInArrearsCapFloorTradeTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2016, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final double NOTIONAL_VALUE = 1.0e6; + private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONAL_VALUE); + private static final OvernightInArrearsCapFloorLeg CAP_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final OvernightInArrearsCapFloorLeg FLOOR_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final OvernightInArrearsCapFloor PRODUCT = OvernightInArrearsCapFloor.of(CAP_LEG); + private static final OvernightInArrearsCapFloor PRODUCT_FLOOR = OvernightInArrearsCapFloor.of(FLOOR_LEG); + private static final AdjustablePayment PREMIUM = + AdjustablePayment.of(CurrencyAmount.of(EUR, NOTIONAL_VALUE), LocalDate.of(2011, 3, 18)); + private static final TradeInfo TRADE_INFO = TradeInfo.builder() + .tradeDate(LocalDate.of(2011, 3, 15)) + .build(); + + //------------------------------------------------------------------------- + @Test + public void test_builder_full() { + OvernightInArrearsCapFloorTrade test = sut(); + assertThat(test.getPremium().get()).isEqualTo(PREMIUM); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getInfo()).isEqualTo(TRADE_INFO); + assertThat(test.withInfo(TRADE_INFO).getInfo()).isEqualTo(TRADE_INFO); + } + + @Test + public void test_builder_min() { + OvernightInArrearsCapFloorTrade test = OvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertThat(test.getPremium()).isNotPresent(); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getInfo()).isEqualTo(TradeInfo.empty()); + } + + //------------------------------------------------------------------------- + @Test + public void test_summarize() { + OvernightInArrearsCapFloorTrade trade = sut(); + PortfolioItemSummary expected = PortfolioItemSummary.builder() + .id(TRADE_INFO.getId().orElse(null)) + .portfolioItemType(PortfolioItemType.TRADE) + .productType(ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR) + .currencies(Currency.EUR) + .description("5Y EUR 1mm Rec Compounded EUR-ESTR Cap 3.25% / Pay Premium : 17Mar11-17Mar16") + .build(); + assertThat(trade.summarize()).isEqualTo(expected); + } + + @Test + public void test_summarize_floor() { + OvernightInArrearsCapFloorTrade trade = OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT_FLOOR) + .build(); + PortfolioItemSummary expected = PortfolioItemSummary.builder() + .id(TRADE_INFO.getId().orElse(null)) + .portfolioItemType(PortfolioItemType.TRADE) + .productType(ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR) + .currencies(Currency.EUR) + .description("5Y EUR 1mm Rec Compounded EUR-ESTR Floor 3.25% : 17Mar11-17Mar16") + .build(); + assertThat(trade.summarize()).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void test_resolve() { + OvernightInArrearsCapFloorTrade test = sut(); + ResolvedOvernightInArrearsCapFloorTrade expected = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT.resolve(REF_DATA)) + .premium(PREMIUM.resolve(REF_DATA)) + .build(); + assertThat(test.resolve(REF_DATA)).isEqualTo(expected); + } + + @Test + public void test_resolve_noPremium() { + OvernightInArrearsCapFloorTrade test = OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .build(); + ResolvedOvernightInArrearsCapFloorTrade expected = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT.resolve(REF_DATA)) + .build(); + assertThat(test.resolve(REF_DATA)).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloorTrade test1 = sut(); + coverImmutableBean(test1); + OvernightInArrearsCapFloor product = OvernightInArrearsCapFloor.of( + OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build()); + OvernightInArrearsCapFloorTrade test2 = OvernightInArrearsCapFloorTrade.builder() + .product(product) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloorTrade test = sut(); + assertSerialization(test); + } + + //------------------------------------------------------------------------- + OvernightInArrearsCapFloorTrade sut() { + return OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java new file mode 100644 index 0000000000..85bf954d3e --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.basics.index.OvernightIndices.GBP_SONIA; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +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; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloorLeg}. + */ +public class ResolvedOvernightInArrearsCapFloorLegTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final double STRIKE = 0.0125; + private static final double NOTIONAL = 1.0e6; + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_1 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 6, 17)) + .unadjustedStartDate(LocalDate.of(2011, 3, 17)) + .unadjustedEndDate(LocalDate.of(2011, 6, 17)) + .paymentDate(LocalDate.of(2011, 6, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 3, 17), + LocalDate.of(2011, 6, 17), + REF_DATA)) + .yearFraction(0.2556) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_2 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_3 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2011, 12, 19)) + .unadjustedStartDate(LocalDate.of(2011, 9, 17)) + .unadjustedEndDate(LocalDate.of(2011, 12, 17)) + .paymentDate(LocalDate.of(2011, 12, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 9, 17), + LocalDate.of(2011, 12, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_4 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 12, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .unadjustedStartDate(LocalDate.of(2011, 12, 17)) + .unadjustedEndDate(LocalDate.of(2012, 3, 17)) + .paymentDate(LocalDate.of(2012, 3, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 12, 17), + LocalDate.of(2012, 3, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + + @Test + public void test_builder() { + ResolvedOvernightInArrearsCapFloorLeg test = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + assertThat(test.getCapletFloorletPeriods()).containsExactly(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4); + assertThat(test.getPayReceive()).isEqualTo(RECEIVE); + assertThat(test.getStartDate()).isEqualTo(PERIOD_1.getStartDate()); + assertThat(test.getEndDate()).isEqualTo(PERIOD_4.getEndDate()); + assertThat(test.getFinalPeriod()).isEqualTo(PERIOD_4); + assertThat(test.getCurrency()).isEqualTo(EUR); + assertThat(test.getIndex()).isEqualTo(EUR_ESTR); + } + + @Test + public void test_builder_fail() { + // two currencies + OvernightInArrearsCapletFloorletPeriod periodGbp = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(GBP) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + assertThatIllegalArgumentException() + .isThrownBy(() -> ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, periodGbp) + .payReceive(RECEIVE) + .build()); + // two indices + OvernightInArrearsCapletFloorletPeriod period = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)).overnightRate(OvernightCompoundedRateComputation.of( + GBP_SONIA, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + assertThatIllegalArgumentException() + .isThrownBy(() -> ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, period) + .payReceive(RECEIVE) + .build()); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloorLeg test1 = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + coverImmutableBean(test1); + ResolvedOvernightInArrearsCapFloorLeg test2 = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_2, PERIOD_3) + .payReceive(PAY) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloorLeg test = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java new file mode 100644 index 0000000000..07c38ac87f --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static com.opengamma.strata.product.swap.SwapLegType.FIXED; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.product.rate.FixedRateComputation; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.RateAccrualPeriod; +import com.opengamma.strata.product.swap.RatePaymentPeriod; +import com.opengamma.strata.product.swap.ResolvedSwapLeg; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloor}. + */ +public class ResolvedOvernightInArrearsCapFloorTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final double STRIKE = 0.0125; + private static final double NOTIONAL = 1.0e6; + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_1 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 6, 17)) + .unadjustedStartDate(LocalDate.of(2011, 3, 17)) + .unadjustedEndDate(LocalDate.of(2011, 6, 17)) + .paymentDate(LocalDate.of(2011, 6, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 3, 17), + LocalDate.of(2011, 6, 17), + REF_DATA)) + .yearFraction(0.2556) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_2 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_3 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2011, 12, 19)) + .unadjustedStartDate(LocalDate.of(2011, 9, 17)) + .unadjustedEndDate(LocalDate.of(2011, 12, 17)) + .paymentDate(LocalDate.of(2011, 12, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 9, 17), + LocalDate.of(2011, 12, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_4 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 12, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .unadjustedStartDate(LocalDate.of(2011, 12, 17)) + .unadjustedEndDate(LocalDate.of(2012, 3, 17)) + .paymentDate(LocalDate.of(2012, 3, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 12, 17), + LocalDate.of(2012, 3, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + static final ResolvedOvernightInArrearsCapFloorLeg CAPFLOOR_LEG = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + + private static final double RATE = 0.015; + private static final RatePaymentPeriod PAY_PERIOD_1 = RatePaymentPeriod.builder() + .paymentDate(LocalDate.of(2011, 9, 21)) + .accrualPeriods(RateAccrualPeriod.builder() + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .yearFraction(0.517) + .rateComputation(FixedRateComputation.of(RATE)) + .build()) + .dayCount(ACT_365F) + .currency(EUR) + .notional(-NOTIONAL) + .build(); + private static final RatePaymentPeriod PAY_PERIOD_2 = RatePaymentPeriod.builder() + .paymentDate(LocalDate.of(2012, 3, 21)) + .accrualPeriods(RateAccrualPeriod.builder() + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .yearFraction(0.505) + .rateComputation(FixedRateComputation.of(RATE)) + .build()) + .dayCount(ACT_365F) + .currency(EUR) + .notional(-NOTIONAL) + .build(); + static final ResolvedSwapLeg PAY_LEG = ResolvedSwapLeg.builder() + .paymentPeriods(PAY_PERIOD_1, PAY_PERIOD_2) + .type(FIXED) + .payReceive(PAY) + .build(); + + //------------------------------------------------------------------------- + @Test + public void test_of_oneLeg() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg()).isNotPresent(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloor test1 = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + coverImmutableBean(test1); + ResolvedOvernightInArrearsCapFloorLeg capFloor = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1) + .payReceive(PAY) + .build(); + ResolvedOvernightInArrearsCapFloor test2 = ResolvedOvernightInArrearsCapFloor.of(capFloor); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java new file mode 100644 index 0000000000..f51940a15b --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.collect.TestHelper.date; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.product.TradeInfo; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloorTrade}. + */ +public class ResolvedOvernightInArrearsCapFloorTradeTest { + + private static final TradeInfo TRADE_INFO = TradeInfo.of(date(2016, 6, 30)); + private static final ResolvedOvernightInArrearsCapFloor PRODUCT = ResolvedOvernightInArrearsCapFloor.of( + ResolvedOvernightInArrearsCapFloorTest.CAPFLOOR_LEG, + ResolvedOvernightInArrearsCapFloorTest.PAY_LEG); + private static final Payment PREMIUM = Payment.of(CurrencyAmount.of(EUR, -0.001 * 1.0e6), date(2016, 7, 2)); + + //------------------------------------------------------------------------- + @Test + public void test_builder() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertThat(test.getInfo()).isEqualTo(TradeInfo.empty()); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getPremium()).isEqualTo(Optional.empty()); + } + + @Test + public void test_builder_full() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + assertThat(test.getInfo()).isEqualTo(TRADE_INFO); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getPremium()).isEqualTo(Optional.of(PREMIUM)); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + coverImmutableBean(test); + ResolvedOvernightInArrearsCapFloorTrade test2 = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + coverBeanEquals(test, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertSerialization(test); + } + +}