Skip to content

Fix overnight future contract spec #2719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,6 +72,13 @@ public final class ImmutableOvernightFutureContractSpec
*/
@PropertyDefinition(validate = "notNull")
private final DateSequence dateSequence;
/**
* The accrual period of the future.
* <p>
* This is the period of time over which the daily interest rate fixing is observed.
*/
@PropertyDefinition(get = "optional")
Copy link
Contributor Author

@brianweller89 brianweller89 May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer to make this non-optional - but that would make this class non backwards compatible. The class is used in a couple of internal repos, but we could easily update the usages to add the Period

private final Period accrualPeriod;
/**
* The method of accruing Overnight interest.
*/
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If accrualPeriod is not optional then can remove this fallback

}
}

//-------------------------------------------------------------------------
@Override
public String toString() {
Expand All @@ -237,6 +255,7 @@ public String toString() {
"name",
"index",
"dateSequence",
"accrualPeriod",
"accrualMethod",
"startDateAdjustment",
"endDateAdjustment",
Expand All @@ -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(),
Expand Down Expand Up @@ -281,6 +301,7 @@ private ImmutableOvernightFutureContractSpec(
String name,
OvernightIndex index,
DateSequence dateSequence,
Period accrualPeriod,
OvernightAccrualMethod accrualMethod,
BusinessDayAdjustment startDateAdjustment,
DaysAdjustment endDateAdjustment,
Expand All @@ -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;
Expand Down Expand Up @@ -343,6 +365,17 @@ public DateSequence getDateSequence() {
return dateSequence;
}

//-----------------------------------------------------------------------
/**
* Gets the accrual period of the future.
* <p>
* 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<Period> getAccrualPeriod() {
return Optional.ofNullable(accrualPeriod);
}

//-----------------------------------------------------------------------
/**
* Gets the method of accruing Overnight interest.
Expand Down Expand Up @@ -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) &&
Expand All @@ -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);
Expand All @@ -454,6 +489,7 @@ public static final class Builder extends DirectFieldsBeanBuilder<ImmutableOvern
private String name;
private OvernightIndex index;
private DateSequence dateSequence;
private Period accrualPeriod;
private OvernightAccrualMethod accrualMethod;
private BusinessDayAdjustment startDateAdjustment;
private DaysAdjustment endDateAdjustment;
Expand All @@ -474,6 +510,7 @@ private Builder(ImmutableOvernightFutureContractSpec beanToCopy) {
this.name = beanToCopy.getName();
this.index = beanToCopy.getIndex();
this.dateSequence = beanToCopy.getDateSequence();
this.accrualPeriod = beanToCopy.accrualPeriod;
this.accrualMethod = beanToCopy.getAccrualMethod();
this.startDateAdjustment = beanToCopy.getStartDateAdjustment();
this.endDateAdjustment = beanToCopy.getEndDateAdjustment();
Expand All @@ -491,6 +528,8 @@ public Object get(String propertyName) {
return index;
case -258065009: // dateSequence
return dateSequence;
case -1249900464: // accrualPeriod
return accrualPeriod;
case -1335729296: // accrualMethod
return accrualMethod;
case -1235962691: // startDateAdjustment
Expand Down Expand Up @@ -518,6 +557,9 @@ public Builder set(String propertyName, Object newValue) {
case -258065009: // dateSequence
this.dateSequence = (DateSequence) newValue;
break;
case -1249900464: // accrualPeriod
this.accrualPeriod = (Period) newValue;
break;
case -1335729296: // accrualMethod
this.accrualMethod = (OvernightAccrualMethod) newValue;
break;
Expand Down Expand Up @@ -552,6 +594,7 @@ public ImmutableOvernightFutureContractSpec build() {
name,
index,
dateSequence,
accrualPeriod,
accrualMethod,
startDateAdjustment,
endDateAdjustment,
Expand Down Expand Up @@ -599,6 +642,18 @@ public Builder dateSequence(DateSequence dateSequence) {
return this;
}

/**
* Sets the accrual period of the future.
* <p>
* 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
Expand Down Expand Up @@ -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(' ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be populated for all constants in StandardOvernightFutureContractSpecs for consistency

.notional(1_000_000d)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key thing that is changing. Prior to the change this test would have incorrectly returned a September date as the end date for the July future

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this PR cover all case?
Is the start date always interpretted as being at the start of the date sequence, or can it be part-way through? (ie. I think this PR works because 3 months < 6 serial, but will break in more complex cases)

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[][] {
Expand Down