Skip to content
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

Enable leniency of dates in value schedule #1646

Merged
merged 1 commit into from Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -6,6 +6,7 @@
package com.opengamma.strata.basics.value;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
Expand Down Expand Up @@ -183,8 +184,13 @@ private DoubleArray resolveSteps(List<SchedulePeriod> periods, RollConvention ro
// expand ValueStep to array of adjustments matching the periods
// the steps are not sorted, so use fixed size array to absorb incoming data
ValueAdjustment[] expandedSteps = new ValueAdjustment[size];
List<ValueStep> invalidSteps = new ArrayList<>();
for (ValueStep step : resolvedSteps) {
int index = step.findIndex(periods);
if (index < 0) {
invalidSteps.add(step);
continue;
}
if (expandedSteps[index] != null && !expandedSteps[index].equals(step.getValue())) {
throw new IllegalArgumentException(Messages.format(
"Invalid ValueSchedule, two steps resolved to the same schedule period starting on {}, schedule defined as {}",
Expand All @@ -200,6 +206,15 @@ private DoubleArray resolveSteps(List<SchedulePeriod> periods, RollConvention ro
}
result[i] = value;
}
// ensure that invalid steps cause no changes
for (ValueStep step : invalidSteps) {
double baseValue = result[step.findPreviousIndex(periods)];
double adjusted = step.getValue().adjust(baseValue);
if (adjusted != baseValue) {
throw new IllegalArgumentException("ValueStep date does not match a period boundary: " + step.getDate().get());
}
}
// return result
return DoubleArray.ofUnsafe(result);
}

Expand Down
Expand Up @@ -143,10 +143,38 @@ int findIndex(List<SchedulePeriod> periods) {
return i;
}
}
throw new IllegalArgumentException("ValueStep date does not match a period boundary: " + date);
return -1;
}
}

/**
* Finds the index of the previous value step in the schedule.
* <p>
* This will only be called on a date-based step.
*
* @param periods the list of schedule periods, at least size 1, only date-based
* @return the index of the schedule period
*/
int findPreviousIndex(List<SchedulePeriod> periods) {
SchedulePeriod firstPeriod = periods.get(0);
if (date.isBefore(firstPeriod.getUnadjustedStartDate())) {
throw new IllegalArgumentException(
"ValueStep date is before the start of the schedule: " + date + " < " + firstPeriod.getUnadjustedStartDate());
}
for (int i = 1; i < periods.size(); i++) {
SchedulePeriod period = periods.get(i);
if (period.getUnadjustedStartDate().isAfter(date)) {
return i - 1;
}
}
SchedulePeriod lastPeriod = periods.get(periods.size() - 1);
if (date.isAfter(lastPeriod.getUnadjustedEndDate())) {
throw new IllegalArgumentException(
"ValueStep date is after the end of the schedule: " + date + " > " + lastPeriod.getUnadjustedEndDate());
}
return periods.size() - 1;
}

//-------------------------------------------------------------------------
@ImmutableValidator
private void validate() {
Expand Down
Expand Up @@ -171,6 +171,16 @@ public void test_resolveValues_dateBased_matchAdjusted() {
assertEquals(test2.resolveValues(SCHEDULE), DoubleArray.of(200d, 300d, 400d));
}

public void test_resolveValues_dateBased_ignoreExcess() {
ValueStep step1 = ValueStep.of(date(2014, 2, 1), ValueAdjustment.ofReplace(300d));
ValueStep step2 = ValueStep.of(date(2014, 2, 15), ValueAdjustment.ofReplace(300d)); // no change to value
ValueStep step3 = ValueStep.of(date(2014, 3, 1), ValueAdjustment.ofReplace(400d));
ValueStep step4 = ValueStep.of(date(2014, 3, 15), ValueAdjustment.ofDeltaAmount(0d)); // no change to value
ValueStep step5 = ValueStep.of(date(2014, 4, 1), ValueAdjustment.ofMultiplier(1d));
ValueSchedule test = ValueSchedule.of(200d, ImmutableList.of(step1, step2, step3, step4, step5));
assertEquals(test.resolveValues(SCHEDULE), DoubleArray.of(200d, 300d, 400d));
}

public void test_resolveValues_indexBased() {
ValueStep step1 = ValueStep.of(1, ValueAdjustment.ofReplace(300d));
ValueStep step2 = ValueStep.of(2, ValueAdjustment.ofReplace(400d));
Expand Down Expand Up @@ -214,10 +224,22 @@ public void test_resolveValues_indexBased_indexTooBig() {
assertThrowsIllegalArg(() -> test.resolveValues(SCHEDULE));
}

public void test_resolveValues_dateBased_dateInvalid() {
public void test_resolveValues_dateBased_invalidChangeValue() {
ValueStep step = ValueStep.of(date(2014, 4, 1), ValueAdjustment.ofReplace(300d));
ValueSchedule test = ValueSchedule.of(200d, ImmutableList.of(step));
assertThrowsIllegalArg(() -> test.resolveValues(SCHEDULE));
assertThrowsIllegalArg(() -> test.resolveValues(SCHEDULE), "ValueStep date does not match a period boundary.*");
}

public void test_resolveValues_dateBased_invalidDateBefore() {
ValueStep step = ValueStep.of(date(2013, 12, 31), ValueAdjustment.ofReplace(300d));
ValueSchedule test = ValueSchedule.of(200d, ImmutableList.of(step));
assertThrowsIllegalArg(() -> test.resolveValues(SCHEDULE), "ValueStep date is before the start of the schedule.*");
}

public void test_resolveValues_dateBased_invalidDateAfter() {
ValueStep step = ValueStep.of(date(2014, 4, 3), ValueAdjustment.ofReplace(300d));
ValueSchedule test = ValueSchedule.of(200d, ImmutableList.of(step));
assertThrowsIllegalArg(() -> test.resolveValues(SCHEDULE), "ValueStep date is after the end of the schedule.*");
}

//-------------------------------------------------------------------------
Expand Down