diff --git a/modules/product/src/main/java/com/opengamma/strata/product/index/type/ImmutableOvernightFutureContractSpec.java b/modules/product/src/main/java/com/opengamma/strata/product/index/type/ImmutableOvernightFutureContractSpec.java index 7195b8747b..f75b212ad2 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/index/type/ImmutableOvernightFutureContractSpec.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/index/type/ImmutableOvernightFutureContractSpec.java @@ -10,8 +10,10 @@ import java.io.Serializable; import java.time.LocalDate; +import java.time.Period; import java.time.YearMonth; import java.util.NoSuchElementException; +import java.util.Optional; import org.joda.beans.ImmutableBean; import org.joda.beans.JodaBeanUtils; @@ -70,6 +72,13 @@ public final class ImmutableOvernightFutureContractSpec */ @PropertyDefinition(validate = "notNull") private final DateSequence dateSequence; + /** + * The accrual period of the future. + *

+ * This is the period of time over which the daily interest rate fixing is observed. + */ + @PropertyDefinition(get = "optional") + private final Period accrualPeriod; /** * The method of accruing Overnight interest. */ @@ -150,7 +159,7 @@ private OvernightFutureTrade createTrade( LocalDate startDate, ReferenceData refData) { - LocalDate nextReferenceDate = dateSequence.baseSequence().next(startDate); + LocalDate nextReferenceDate = getFinalReferenceDate(startDate); double accrualFactor = startDate.withDayOfMonth(1).until(nextReferenceDate.withDayOfMonth(1), MONTHS) / 12d; LocalDate endDate = endDateAdjustment.adjust(nextReferenceDate, refData); LocalDate lastTradeDate = lastTradeDateAdjustment.adjust(nextReferenceDate, refData); @@ -191,7 +200,7 @@ private OvernightFuturePosition createPosition( LocalDate startDate, ReferenceData refData) { - LocalDate nextReferenceDate = dateSequence.baseSequence().next(startDate); + LocalDate nextReferenceDate = getFinalReferenceDate(startDate); double accrualFactor = startDate.withDayOfMonth(1).until(nextReferenceDate.withDayOfMonth(1), MONTHS) / 12d; LocalDate endDate = endDateAdjustment.adjust(nextReferenceDate, refData); LocalDate lastTradeDate = lastTradeDateAdjustment.adjust(nextReferenceDate, refData); @@ -216,10 +225,19 @@ public LocalDate calculateReferenceDate(LocalDate tradeDate, SequenceDate sequen @Override public LocalDate calculateLastFixingDate(LocalDate referenceDate, ReferenceData refData) { - LocalDate nextReferenceDate = dateSequence.baseSequence().next(referenceDate); + LocalDate nextReferenceDate = getFinalReferenceDate(referenceDate); return endDateAdjustment.adjust(nextReferenceDate, refData); } + private LocalDate getFinalReferenceDate(LocalDate referenceDate) { + if (getAccrualPeriod().isPresent()) { + YearMonth finalReferenceMonth = YearMonth.from(referenceDate.plus(getAccrualPeriod().get())); + return dateSequence.dateMatching(finalReferenceMonth); + } else { + return dateSequence.baseSequence().next(referenceDate); + } + } + //------------------------------------------------------------------------- @Override public String toString() { @@ -237,6 +255,7 @@ public String toString() { "name", "index", "dateSequence", + "accrualPeriod", "accrualMethod", "startDateAdjustment", "endDateAdjustment", @@ -246,6 +265,7 @@ public String toString() { b -> b.getName(), b -> b.getIndex(), b -> b.getDateSequence(), + b -> b.accrualPeriod, b -> b.getAccrualMethod(), b -> b.getStartDateAdjustment(), b -> b.getEndDateAdjustment(), @@ -281,6 +301,7 @@ private ImmutableOvernightFutureContractSpec( String name, OvernightIndex index, DateSequence dateSequence, + Period accrualPeriod, OvernightAccrualMethod accrualMethod, BusinessDayAdjustment startDateAdjustment, DaysAdjustment endDateAdjustment, @@ -296,6 +317,7 @@ private ImmutableOvernightFutureContractSpec( this.name = name; this.index = index; this.dateSequence = dateSequence; + this.accrualPeriod = accrualPeriod; this.accrualMethod = accrualMethod; this.startDateAdjustment = startDateAdjustment; this.endDateAdjustment = endDateAdjustment; @@ -343,6 +365,17 @@ public DateSequence getDateSequence() { return dateSequence; } + //----------------------------------------------------------------------- + /** + * Gets the accrual period of the future. + *

+ * This is the period of time over which the daily interest rate fixing is observed. + * @return the optional value of the property, not null + */ + public Optional getAccrualPeriod() { + return Optional.ofNullable(accrualPeriod); + } + //----------------------------------------------------------------------- /** * Gets the method of accruing Overnight interest. @@ -422,6 +455,7 @@ public boolean equals(Object obj) { return JodaBeanUtils.equal(name, other.name) && JodaBeanUtils.equal(index, other.index) && JodaBeanUtils.equal(dateSequence, other.dateSequence) && + JodaBeanUtils.equal(accrualPeriod, other.accrualPeriod) && JodaBeanUtils.equal(accrualMethod, other.accrualMethod) && JodaBeanUtils.equal(startDateAdjustment, other.startDateAdjustment) && JodaBeanUtils.equal(endDateAdjustment, other.endDateAdjustment) && @@ -437,6 +471,7 @@ public int hashCode() { hash = hash * 31 + JodaBeanUtils.hashCode(name); hash = hash * 31 + JodaBeanUtils.hashCode(index); hash = hash * 31 + JodaBeanUtils.hashCode(dateSequence); + hash = hash * 31 + JodaBeanUtils.hashCode(accrualPeriod); hash = hash * 31 + JodaBeanUtils.hashCode(accrualMethod); hash = hash * 31 + JodaBeanUtils.hashCode(startDateAdjustment); hash = hash * 31 + JodaBeanUtils.hashCode(endDateAdjustment); @@ -454,6 +489,7 @@ public static final class Builder extends DirectFieldsBeanBuilder + * This is the period of time over which the daily interest rate fixing is observed. + * @param accrualPeriod the new value + * @return this, for chaining, not null + */ + public Builder accrualPeriod(Period accrualPeriod) { + this.accrualPeriod = accrualPeriod; + return this; + } + /** * Sets the method of accruing Overnight interest. * @param accrualMethod the new value, not null @@ -670,11 +725,12 @@ public Builder notional(double notional) { //----------------------------------------------------------------------- @Override public String toString() { - StringBuilder buf = new StringBuilder(288); + StringBuilder buf = new StringBuilder(320); buf.append("ImmutableOvernightFutureContractSpec.Builder{"); buf.append("name").append('=').append(JodaBeanUtils.toString(name)).append(',').append(' '); buf.append("index").append('=').append(JodaBeanUtils.toString(index)).append(',').append(' '); buf.append("dateSequence").append('=').append(JodaBeanUtils.toString(dateSequence)).append(',').append(' '); + buf.append("accrualPeriod").append('=').append(JodaBeanUtils.toString(accrualPeriod)).append(',').append(' '); buf.append("accrualMethod").append('=').append(JodaBeanUtils.toString(accrualMethod)).append(',').append(' '); buf.append("startDateAdjustment").append('=').append(JodaBeanUtils.toString(startDateAdjustment)).append(',').append(' '); buf.append("endDateAdjustment").append('=').append(JodaBeanUtils.toString(endDateAdjustment)).append(',').append(' '); diff --git a/modules/product/src/main/java/com/opengamma/strata/product/index/type/StandardOvernightFutureContractSpecs.java b/modules/product/src/main/java/com/opengamma/strata/product/index/type/StandardOvernightFutureContractSpecs.java index e734411a5b..a084767b52 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/index/type/StandardOvernightFutureContractSpecs.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/index/type/StandardOvernightFutureContractSpecs.java @@ -22,6 +22,8 @@ import static com.opengamma.strata.product.swap.OvernightAccrualMethod.AVERAGED_DAILY; import static com.opengamma.strata.product.swap.OvernightAccrualMethod.COMPOUNDED; +import java.time.Period; + import com.opengamma.strata.basics.date.BusinessDayAdjustment; import com.opengamma.strata.basics.date.DaysAdjustment; @@ -183,8 +185,9 @@ final class StandardOvernightFutureContractSpecs { ImmutableOvernightFutureContractSpec.builder() .name("USD-SOFR-3M-IMM-CME") .index(USD_SOFR) - .dateSequence(QUARTERLY_IMM) + .dateSequence(QUARTERLY_IMM_6_SERIAL) .accrualMethod(COMPOUNDED) + .accrualPeriod(Period.ofMonths(3)) .notional(1_000_000d) .build(); diff --git a/modules/product/src/test/java/com/opengamma/strata/product/index/type/OvernightFutureContractSpecTest.java b/modules/product/src/test/java/com/opengamma/strata/product/index/type/OvernightFutureContractSpecTest.java index b6bed3210f..4e5aa9b114 100644 --- a/modules/product/src/test/java/com/opengamma/strata/product/index/type/OvernightFutureContractSpecTest.java +++ b/modules/product/src/test/java/com/opengamma/strata/product/index/type/OvernightFutureContractSpecTest.java @@ -330,6 +330,21 @@ public void test_createPosition_usdFedFund1mCme() { assertThat(trade.getProduct().getNotional()).isEqualTo(5_000_000d); } + @Test + public void test_createPosition_usdSofr3mCme() { + OvernightFutureContractSpec test = OvernightFutureContractSpecs.USD_SOFR_3M_IMM_CME; + OvernightFuturePosition trade = test.createPosition(SecurityId.of("OG", "1"), YearMonth.of(2025, 7), 20, REF_DATA); + assertThat(trade.getCurrency()).isEqualTo(Currency.USD); + assertThat(trade.getQuantity()).isEqualTo(20); + assertThat(trade.getProduct().getIndex()).isEqualTo(USD_SOFR); + assertThat(trade.getProduct().getAccrualMethod()).isEqualTo(COMPOUNDED); + assertThat(trade.getProduct().getAccrualFactor()).isEqualTo(3 / 12d); + assertThat(trade.getProduct().getStartDate()).isEqualTo(date(2025, 7, 16)); + assertThat(trade.getProduct().getEndDate()).isEqualTo(date(2025, 10, 14)); + assertThat(trade.getProduct().getLastTradeDate()).isEqualTo(date(2025, 10, 14)); + assertThat(trade.getProduct().getNotional()).isEqualTo(1_000_000d); + } + //------------------------------------------------------------------------- public static Object[][] data_name() { return new Object[][] {