-
Notifications
You must be signed in to change notification settings - Fork 297
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
* <p> | ||
* 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
|
@@ -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. | ||
* <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. | ||
|
@@ -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<ImmutableOvern | |
private String name; | ||
private OvernightIndex index; | ||
private DateSequence dateSequence; | ||
private Period accrualPeriod; | ||
private OvernightAccrualMethod accrualMethod; | ||
private BusinessDayAdjustment startDateAdjustment; | ||
private DaysAdjustment endDateAdjustment; | ||
|
@@ -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(); | ||
|
@@ -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 | ||
|
@@ -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; | ||
|
@@ -552,6 +594,7 @@ public ImmutableOvernightFutureContractSpec build() { | |
name, | ||
index, | ||
dateSequence, | ||
accrualPeriod, | ||
accrualMethod, | ||
startDateAdjustment, | ||
endDateAdjustment, | ||
|
@@ -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 | ||
|
@@ -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(' '); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be populated for all constants in |
||
.notional(1_000_000d) | ||
.build(); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this PR cover all case? |
||
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[][] { | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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