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 extends OvernightInArrearsCapFloor> builder() {
+ return new OvernightInArrearsCapFloor.Builder();
+ }
+
+ @Override
+ public Class extends OvernightInArrearsCapFloor> 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 extends OvernightInArrearsCapFloorLeg> 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 extends OvernightInArrearsCapFloorTrade> 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 extends ResolvedOvernightInArrearsCapFloor> builder() {
+ return new ResolvedOvernightInArrearsCapFloor.Builder();
+ }
+
+ @Override
+ public Class extends ResolvedOvernightInArrearsCapFloor> 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 extends ResolvedOvernightInArrearsCapFloorLeg> 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 extends ResolvedOvernightInArrearsCapFloorTrade> 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);
+ }
+
+}