mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Expanded the verify() function for the IRS Contract
This commit is contained in:
parent
c6fab1c642
commit
62e7dc583e
@ -19,15 +19,24 @@ open class UnknownType() {
|
||||
return (other is UnknownType)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return 1
|
||||
}
|
||||
override fun hashCode() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Event superclass - everything happens on a date.
|
||||
*/
|
||||
open class Event(val date: LocalDate)
|
||||
open class Event(val date: LocalDate) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Event) return false
|
||||
|
||||
if (date != other.date) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode() = Objects.hash(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Top level PaymentEvent class - represents an obligation to pay an amount on a given date, which may be either in the past or the future.
|
||||
@ -58,14 +67,30 @@ abstract class RatePaymentEvent(date: LocalDate,
|
||||
|
||||
abstract val flow: Amount
|
||||
|
||||
val days: Int get() =
|
||||
calculateDaysBetween(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
|
||||
val days: Int get() = calculateDaysBetween(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
|
||||
|
||||
val dayCountFactor: BigDecimal get() =
|
||||
// TODO : Fix below (use daycount convention for division)
|
||||
(BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP)
|
||||
// TODO : Fix below (use daycount convention for division, not hardcoded 360 etc)
|
||||
val dayCountFactor: BigDecimal get() = (BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP)
|
||||
|
||||
open fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow"
|
||||
open fun asCSV() = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is RatePaymentEvent) return false
|
||||
|
||||
if (accrualStartDate != other.accrualStartDate) return false
|
||||
if (accrualEndDate != other.accrualEndDate) return false
|
||||
if (dayCountBasisDay != other.dayCountBasisDay) return false
|
||||
if (dayCountBasisYear != other.dayCountBasisYear) return false
|
||||
if (notional != other.notional) return false
|
||||
if (rate != other.rate) return false
|
||||
// if (flow != other.flow) return false // Flow is derived
|
||||
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(accrualStartDate, accrualEndDate, dayCountBasisDay,
|
||||
dayCountBasisYear, notional, rate)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,9 +139,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
||||
return Amount(dayCountFactor.times(BigDecimal(notional.pennies)).times(v).toLong(), notional.currency)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FloatingPaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate (fix on $fixingDate): $flow"
|
||||
}
|
||||
override fun toString(): String = "FloatingPaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate (fix on $fixingDate): $flow"
|
||||
|
||||
override fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$fixingDate,$rate,$flow"
|
||||
|
||||
@ -126,6 +149,26 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
||||
fun withNewRate(newRate: Rate): FloatingRatePaymentEvent =
|
||||
FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay,
|
||||
dayCountBasisYear, fixingDate, notional, newRate)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
other as FloatingRatePaymentEvent
|
||||
if (fixingDate != other.fixingDate) return false
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(fixingDate)
|
||||
|
||||
// Can't autogenerate as not a data class :-(
|
||||
fun copy(date: LocalDate = this.date,
|
||||
accrualStartDate: LocalDate = this.accrualStartDate,
|
||||
accrualEndDate: LocalDate = this.accrualEndDate,
|
||||
dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear,
|
||||
fixingDate: LocalDate = this.fixingDate,
|
||||
notional: Amount = this.notional,
|
||||
rate: Rate = this.rate) = FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, fixingDate, notional, rate)
|
||||
}
|
||||
|
||||
|
||||
@ -191,18 +234,13 @@ class InterestRateSwap() : Contract {
|
||||
/**
|
||||
* Returns a copy after modifying (applying) the fixing for that date.
|
||||
*/
|
||||
fun applyFixing(date: LocalDate, newRate: Rate): Calculation {
|
||||
fun applyFixing(date: LocalDate, newRate: FixedRate): Calculation {
|
||||
val paymentEvent = getFixing(date)
|
||||
val newFloatingLPS = floatingLegPaymentSchedule + (paymentEvent.date to paymentEvent.withNewRate(newRate))
|
||||
return Calculation(expression = expression,
|
||||
floatingLegPaymentSchedule = newFloatingLPS,
|
||||
fixedLegPaymentSchedule = fixedLegPaymentSchedule)
|
||||
}
|
||||
|
||||
fun exportSchedule() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class CommonLeg(
|
||||
@ -212,13 +250,13 @@ class InterestRateSwap() : Contract {
|
||||
val effectiveDateAdjustment: DateRollConvention?,
|
||||
val terminationDate: LocalDate,
|
||||
val terminationDateAdjustment: DateRollConvention?,
|
||||
var dayCountBasisDay: DayCountBasisDay,
|
||||
var dayCountBasisYear: DayCountBasisYear,
|
||||
var dayInMonth: Int,
|
||||
var paymentRule: PaymentRule,
|
||||
var paymentDelay: Int,
|
||||
var paymentCalendar: BusinessCalendar,
|
||||
var interestPeriodAdjustment: AccrualAdjustment
|
||||
val dayCountBasisDay: DayCountBasisDay,
|
||||
val dayCountBasisYear: DayCountBasisYear,
|
||||
val dayInMonth: Int,
|
||||
val paymentRule: PaymentRule,
|
||||
val paymentDelay: Int,
|
||||
val paymentCalendar: BusinessCalendar,
|
||||
val interestPeriodAdjustment: AccrualAdjustment
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "Notional=$notional,PaymentFrequency=$paymentFrequency,EffectiveDate=$effectiveDate,EffectiveDateAdjustment:$effectiveDateAdjustment,TerminatationDate=$terminationDate," +
|
||||
@ -249,24 +287,9 @@ class InterestRateSwap() : Contract {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = notional.hashCode()
|
||||
result += 31 * result + paymentFrequency.hashCode()
|
||||
result += 31 * result + effectiveDate.hashCode()
|
||||
result += 31 * result + (effectiveDateAdjustment?.hashCode() ?: 0)
|
||||
result += 31 * result + terminationDate.hashCode()
|
||||
result += 31 * result + (terminationDateAdjustment?.hashCode() ?: 0)
|
||||
result += 31 * result + dayCountBasisDay.hashCode()
|
||||
result += 31 * result + dayCountBasisYear.hashCode()
|
||||
result += 31 * result + dayInMonth
|
||||
result += 31 * result + paymentRule.hashCode()
|
||||
result += 31 * result + paymentDelay
|
||||
result += 31 * result + paymentCalendar.hashCode()
|
||||
result += 31 * result + interestPeriodAdjustment.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(notional, paymentFrequency, effectiveDate,
|
||||
effectiveDateAdjustment, terminationDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment)
|
||||
}
|
||||
|
||||
open class FixedLeg(
|
||||
@ -306,13 +329,27 @@ class InterestRateSwap() : Contract {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result += 31 * result + fixedRatePayer.hashCode()
|
||||
result += 31 * result + fixedRate.hashCode()
|
||||
result += 31 * result + rollConvention.hashCode()
|
||||
return result
|
||||
}
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(fixedRatePayer, fixedRate, rollConvention)
|
||||
|
||||
// Can't autogenerate as not a data class :-(
|
||||
fun copy(fixedRatePayer: Party = this.fixedRatePayer,
|
||||
notional: Amount = this.notional,
|
||||
paymentFrequency: Frequency = this.paymentFrequency,
|
||||
effectiveDate: LocalDate = this.effectiveDate,
|
||||
effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment,
|
||||
terminationDate: LocalDate = this.terminationDate,
|
||||
terminationDateAdjustment: DateRollConvention? = this.terminationDateAdjustment,
|
||||
dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear,
|
||||
dayInMonth: Int = this.dayInMonth,
|
||||
paymentRule: PaymentRule = this.paymentRule,
|
||||
paymentDelay: Int = this.paymentDelay,
|
||||
paymentCalendar: BusinessCalendar = this.paymentCalendar,
|
||||
interestPeriodAdjustment: AccrualAdjustment = this.interestPeriodAdjustment,
|
||||
fixedRate: FixedRate = this.fixedRate) = FixedLeg(
|
||||
fixedRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate,
|
||||
terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay,
|
||||
paymentCalendar, interestPeriodAdjustment, fixedRate, rollConvention)
|
||||
|
||||
}
|
||||
|
||||
@ -370,62 +407,168 @@ class InterestRateSwap() : Contract {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result += 31 * result + floatingRatePayer.hashCode()
|
||||
result += 31 * result + rollConvention.hashCode()
|
||||
result += 31 * result + fixingRollConvention.hashCode()
|
||||
result += 31 * result + resetDayInMonth
|
||||
result += 31 * result + fixingPeriod.hashCode()
|
||||
result += 31 * result + resetRule.hashCode()
|
||||
result += 31 * result + fixingsPerPayment.hashCode()
|
||||
result += 31 * result + fixingCalendar.hashCode()
|
||||
result += 31 * result + index.hashCode()
|
||||
result += 31 * result + indexSource.hashCode()
|
||||
result += 31 * result + indexTenor.hashCode()
|
||||
return result
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(floatingRatePayer, rollConvention,
|
||||
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment, fixingCalendar,
|
||||
index, indexSource, indexTenor)
|
||||
|
||||
|
||||
fun copy(floatingRatePayer: Party = this.floatingRatePayer,
|
||||
notional: Amount = this.notional,
|
||||
paymentFrequency: Frequency = this.paymentFrequency,
|
||||
effectiveDate: LocalDate = this.effectiveDate,
|
||||
effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment,
|
||||
terminationDate: LocalDate = this.terminationDate,
|
||||
terminationDateAdjustment: DateRollConvention? = this.terminationDateAdjustment,
|
||||
dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear,
|
||||
dayInMonth: Int = this.dayInMonth,
|
||||
paymentRule: PaymentRule = this.paymentRule,
|
||||
paymentDelay: Int = this.paymentDelay,
|
||||
paymentCalendar: BusinessCalendar = this.paymentCalendar,
|
||||
interestPeriodAdjustment: AccrualAdjustment = this.interestPeriodAdjustment,
|
||||
rollConvention: DateRollConvention = this.rollConvention,
|
||||
fixingRollConvention: DateRollConvention = this.fixingRollConvention,
|
||||
resetDayInMonth: Int = this.resetDayInMonth,
|
||||
fixingPeriod: DateOffset = this.fixingPeriod,
|
||||
resetRule: PaymentRule = this.resetRule,
|
||||
fixingsPerPayment: Frequency = this.fixingsPerPayment,
|
||||
fixingCalendar: BusinessCalendar = this.fixingCalendar,
|
||||
index: String = this.index,
|
||||
indexSource: String = this.indexSource,
|
||||
indexTenor: Tenor = this.indexTenor
|
||||
) = FloatingLeg(floatingRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment,
|
||||
terminationDate, terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth,
|
||||
paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment, rollConvention,
|
||||
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment,
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
}
|
||||
|
||||
// These functions may make more sense to use for basket types, but for now let's leave them here
|
||||
fun checkLegDates(legs: Array<CommonLeg>) {
|
||||
requireThat {
|
||||
"Effective date is before termination date" by legs.all { it.effectiveDate < it.terminationDate }
|
||||
"Effective dates are in alignment" by legs.all { it.effectiveDate == legs[0].effectiveDate }
|
||||
"Termination dates are in alignment" by legs.all { it.terminationDate == legs[0].terminationDate }
|
||||
}
|
||||
}
|
||||
|
||||
fun checkLegAmounts(legs: Array<CommonLeg>) {
|
||||
requireThat {
|
||||
"The notional is non zero" by legs.any { it.notional.pennies > (0).toLong() }
|
||||
"The notional for all legs must be the same" by legs.all { it.notional == legs[0].notional }
|
||||
}
|
||||
for (leg: CommonLeg in legs) {
|
||||
if (leg is FixedLeg) {
|
||||
requireThat {
|
||||
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
|
||||
"Fixed leg rate must be positive" by leg.fixedRate.isPositive()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: After business rules discussion, add further checks to the schedules and rates
|
||||
fun checkSchedules(@Suppress("UNUSED_PARAMETER") legs: Array<CommonLeg>): Boolean = true
|
||||
|
||||
fun checkRates(@Suppress("UNUSED_PARAMETER") legs: Array<CommonLeg>): Boolean = true
|
||||
|
||||
/**
|
||||
* verify() with a few examples of what needs to be checked. TODO: Lots more to add.
|
||||
* Compares two schedules of Floating Leg Payments, returns the difference (i.e. omissions in either leg or changes to the values).
|
||||
*/
|
||||
fun getFloatingLegPaymentsDifferences(payments1: Map<LocalDate, Event>, payments2: Map<LocalDate, Event>): List<Pair<LocalDate, Pair<FloatingRatePaymentEvent, FloatingRatePaymentEvent>>> {
|
||||
val diff1 = payments1.filter { payments1[it.key] != payments2[it.key] }
|
||||
val diff2 = payments2.filter { payments1[it.key] != payments2[it.key] }
|
||||
val ret = (diff1.keys + diff2.keys).map { it to Pair(diff1.get(it) as FloatingRatePaymentEvent, diff2.get(it) as FloatingRatePaymentEvent) }
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* verify() with some examples of what needs to be checked.
|
||||
*/
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
|
||||
// Group by Trade ID for in / out states
|
||||
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
||||
|
||||
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
|
||||
val time = tx.commands.getTimestampByName("Mock Company 0", "Timestamping Service", "Bank A")?.midpoint
|
||||
if (time == null) throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
val irs = tx.outStates.filterIsInstance<InterestRateSwap.State>().single()
|
||||
when (command.value) {
|
||||
is Commands.Agree -> {
|
||||
requireThat {
|
||||
"There are no in states for an agreement" by tx.inStates.isEmpty()
|
||||
"The fixed rate is non zero" by (irs.fixedLeg.fixedRate != FixedRate(PercentageRatioUnit("0.0")))
|
||||
"There are events in the fix schedule" by (irs.calculation.fixedLegPaymentSchedule.size > 0)
|
||||
"There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0)
|
||||
// "There are fixes in the schedule" by (irs.calculation.floatingLegPaymentSchedule!!.size > 0)
|
||||
// TODO: shortlist of other tests
|
||||
}
|
||||
}
|
||||
is Commands.Fix -> {
|
||||
requireThat {
|
||||
// TODO: see previous block
|
||||
// "There is a fixing supplied" by false // TODO
|
||||
// "The fixing has been signed by an appropriate oracle" by false // TODO
|
||||
// "The fixing has arrived at the right time" by false
|
||||
// "The net payment has been calculated" by false // TODO : Not sure if this is the right place
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
when (command.value) {
|
||||
is Commands.Agree -> {
|
||||
val irs = outputs.filterIsInstance<InterestRateSwap.State>().single()
|
||||
requireThat {
|
||||
"There are no in states for an agreement" by inputs.isEmpty()
|
||||
"There are events in the fix schedule" by (irs.calculation.fixedLegPaymentSchedule.size > 0)
|
||||
"There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0)
|
||||
"All notionals must be non zero" by ( irs.fixedLeg.notional.pennies > 0 && irs.floatingLeg.notional.pennies > 0)
|
||||
"The fixed leg rate must be positive" by ( irs.fixedLeg.fixedRate.isPositive() )
|
||||
"The currency of the notionals must be the same" by (irs.fixedLeg.notional.currency == irs.floatingLeg.notional.currency)
|
||||
"All leg notionals must be the same" by (irs.fixedLeg.notional == irs.floatingLeg.notional)
|
||||
|
||||
"The effective date is before the termination date for the fixed leg" by (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
|
||||
"The effective date is before the termination date for the floating leg" by (irs.floatingLeg.effectiveDate < irs.floatingLeg.terminationDate)
|
||||
"The effective dates are aligned" by (irs.floatingLeg.effectiveDate == irs.fixedLeg.effectiveDate)
|
||||
"The termination dates are aligned" by (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
|
||||
"The rates are valid" by checkRates(arrayOf(irs.fixedLeg, irs.floatingLeg))
|
||||
"The schedules are valid" by checkSchedules(arrayOf(irs.fixedLeg, irs.floatingLeg))
|
||||
|
||||
|
||||
// TODO: further tests
|
||||
}
|
||||
checkLegAmounts(arrayOf(irs.fixedLeg, irs.floatingLeg))
|
||||
checkLegDates(arrayOf(irs.fixedLeg, irs.floatingLeg))
|
||||
}
|
||||
}
|
||||
is Commands.Pay -> {
|
||||
requireThat {
|
||||
// TODO: see previous block
|
||||
//"A counterparty must be making a payment" by false // TODO
|
||||
// "The right counterparty must be receiving the payment" by false // TODO
|
||||
is Commands.Fix -> {
|
||||
val irs = outputs.filterIsInstance<InterestRateSwap.State>().single()
|
||||
val prevIrs = inputs.filterIsInstance<InterestRateSwap.State>().single()
|
||||
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
|
||||
|
||||
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
|
||||
// we can relay more information back to the user in the case of failure.
|
||||
requireThat {
|
||||
"There is at least one difference in the IRS floating leg payment schedules" by !paymentDifferences.isEmpty()
|
||||
"There is only one change in the IRS floating leg payment schedule" by (paymentDifferences.size == 1)
|
||||
}
|
||||
|
||||
val changedRates = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
|
||||
val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = changedRates
|
||||
val fixCommand = tx.commands.requireSingleCommand<Fix>()
|
||||
val fixValue = fixCommand.value
|
||||
// Need to check that everything is the same apart from the new fixed rate entry.
|
||||
requireThat {
|
||||
"The fixed leg parties are constant" by ( irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
|
||||
"The fixed leg is constant" by (irs.fixedLeg == prevIrs.fixedLeg)
|
||||
"The floating leg is constant" by (irs.floatingLeg == prevIrs.floatingLeg)
|
||||
"The common values are constant" by (irs.common == prevIrs.common)
|
||||
"The fixed leg payment schedule is constant" by (irs.calculation.fixedLegPaymentSchedule == prevIrs.calculation.fixedLegPaymentSchedule)
|
||||
"The expression is unchanged" by (irs.calculation.expression == prevIrs.calculation.expression)
|
||||
"There is only one changed payment in the floating leg" by (paymentDifferences.size == 1)
|
||||
"There changed payment is a floating payment" by (oldFloatingRatePaymentEvent.rate is ReferenceRate)
|
||||
"The new payment is a fixed payment" by (newFixedRatePaymentEvent.rate is FixedRate)
|
||||
"The changed payments dates are aligned" by ( oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
|
||||
"The new payment has the correct rate" by (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
|
||||
"The fixing is for the next required date" by (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
|
||||
"The fix payment has the same currency as the notional" by (newFixedRatePaymentEvent.flow.currency == irs.floatingLeg.notional.currency)
|
||||
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
|
||||
}
|
||||
}
|
||||
is Commands.Pay -> {
|
||||
requireThat {
|
||||
"Payments not supported / verifiable yet" by false
|
||||
}
|
||||
}
|
||||
is Commands.Mature -> {
|
||||
val irs = inputs.filterIsInstance<InterestRateSwap.State>().single()
|
||||
requireThat {
|
||||
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unrecognised verifiable command: ${command.value}")
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unrecognised verifiable command: ${command.value}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -510,6 +653,7 @@ class InterestRateSwap() : Contract {
|
||||
* Just makes printing it out a bit better for those who don't have 80000 column wide monitors.
|
||||
*/
|
||||
fun prettyPrint(): String = toString().replace(",", "\n")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -580,7 +724,7 @@ class InterestRateSwap() : Contract {
|
||||
// TODO: Replace with rates oracle
|
||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
|
||||
tx.addInputState(irs.ref)
|
||||
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, fixing.second)))
|
||||
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second))))
|
||||
tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey))
|
||||
}
|
||||
}
|
||||
|
@ -24,17 +24,14 @@ open class RatioUnit(value: BigDecimal) { // TODO: Discuss this type
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
|
||||
override fun hashCode() = value.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to reprecent a percentage in an unambiguous way.
|
||||
*/
|
||||
open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecimal(percentageAsString).divide(BigDecimal("100"))) {
|
||||
override fun toString(): String = value.times(BigDecimal(100)).toString() + "%"
|
||||
override fun toString() = value.times(BigDecimal(100)).toString() + "%"
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,16 +108,23 @@ open class Rate(val ratioUnit: RatioUnit? = null) {
|
||||
* that have not yet happened. Yet-to-be fixed floating rates need to be equal such that schedules can be tested
|
||||
* for equality.
|
||||
*/
|
||||
override fun hashCode(): Int {
|
||||
return ratioUnit?.hashCode() ?: 0
|
||||
}
|
||||
override fun hashCode() = ratioUnit?.hashCode() ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* A very basic subclass to represent a fixed rate.
|
||||
*/
|
||||
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
|
||||
|
||||
constructor(otherRate: Rate) : this(ratioUnit = otherRate.ratioUnit!!)
|
||||
|
||||
override fun toString(): String = "$ratioUnit"
|
||||
|
||||
fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0")
|
||||
|
||||
override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other)
|
||||
|
||||
override fun hashCode() = super.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,9 +6,9 @@
|
||||
"currency": "USD"
|
||||
},
|
||||
"paymentFrequency": "SemiAnnual",
|
||||
"effectiveDate": "2016-03-16",
|
||||
"effectiveDate": "2016-03-11",
|
||||
"effectiveDateAdjustment": null,
|
||||
"terminationDate": "2026-03-16",
|
||||
"terminationDate": "2026-03-11",
|
||||
"terminationDateAdjustment": null,
|
||||
"fixedRate": {
|
||||
"ratioUnit": {
|
||||
@ -31,9 +31,9 @@
|
||||
"currency": "USD"
|
||||
},
|
||||
"paymentFrequency": "Quarterly",
|
||||
"effectiveDate": "2016-03-12",
|
||||
"effectiveDate": "2016-03-11",
|
||||
"effectiveDateAdjustment": null,
|
||||
"terminationDate": "2026-03-12",
|
||||
"terminationDate": "2026-03-11",
|
||||
"terminationDateAdjustment": null,
|
||||
"dayCountBasisDay": "D30",
|
||||
"dayCountBasisYear": "Y360",
|
||||
|
@ -5,7 +5,8 @@ ICE LIBOR 2016-03-16 2M = 0.655
|
||||
EURIBOR 2016-03-15 1M = 0.123
|
||||
EURIBOR 2016-03-15 2M = 0.111
|
||||
|
||||
ICE LIBOR 2016-03-06 3M = 0.0063515
|
||||
# Previous fixings
|
||||
ICE LIBOR 2016-03-07 3M = 0.0063516
|
||||
ICE LIBOR 2016-03-07 3M = 0.0063516
|
||||
ICE LIBOR 2016-03-08 3M = 0.0063517
|
||||
ICE LIBOR 2016-03-09 3M = 0.0063518
|
||||
|
@ -113,6 +113,8 @@ interface KeyManagementService {
|
||||
|
||||
fun toPrivate(publicKey: PublicKey) = keys[publicKey] ?: throw IllegalStateException("No private key known for requested public key")
|
||||
|
||||
fun toKeyPair(publicKey: PublicKey) = KeyPair(publicKey, toPrivate(publicKey))
|
||||
|
||||
/** Generates a new random key and adds it to the exposed map. */
|
||||
fun freshKey(): KeyPair
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import core.utilities.ANSIProgressRenderer
|
||||
import core.utilities.ProgressTracker
|
||||
import demos.DemoClock
|
||||
import protocols.TwoPartyDealProtocol
|
||||
import java.security.KeyPair
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
@ -98,7 +99,13 @@ object UpdateBusinessDayProtocol {
|
||||
progressTracker.childrenFor[FIXING] = TwoPartyDealProtocol.Primary.tracker()
|
||||
progressTracker.currentStep = FIXING
|
||||
|
||||
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.timestampingNodes[0], dealStateAndRef, serviceHub.keyManagementService.freshKey(), sessionID, progressTracker.childrenFor[FIXING]!!)
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val deal: InterestRateSwap.State = dealStateAndRef.state
|
||||
val myOldParty = deal.parties.single { it.name == myName }
|
||||
val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey)
|
||||
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.timestampingNodes[0], dealStateAndRef,
|
||||
keyPair,
|
||||
sessionID, progressTracker.childrenFor[FIXING]!!)
|
||||
val result = subProtocol(participant)
|
||||
return result.tx.outRef(0)
|
||||
}
|
||||
|
@ -367,10 +367,9 @@ object TwoPartyDealProtocol {
|
||||
val deal: T = dealToFix.state
|
||||
val myOldParty = deal.parties.single { it.name == myName }
|
||||
val theirOldParty = deal.parties.single { it.name != myName }
|
||||
val myNewKey = serviceHub.keyManagementService.freshKey().public
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val newDeal = deal.withPublicKey(myOldParty, myNewKey).withPublicKey(theirOldParty, handshake.publicKey) as T
|
||||
val newDeal = deal
|
||||
val oldRef = dealToFix.ref
|
||||
|
||||
val ptx = TransactionBuilder()
|
||||
@ -386,7 +385,7 @@ object TwoPartyDealProtocol {
|
||||
}
|
||||
subProtocol(addFixing)
|
||||
|
||||
return Pair(ptx, arrayListOf(myNewKey))
|
||||
return Pair(ptx, arrayListOf(myOldParty.owningKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import core.*
|
||||
import core.node.services.DummyTimestampingAuthority
|
||||
import core.testutils.*
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
|
||||
@ -15,9 +16,9 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
fixedRatePayer = MEGA_CORP,
|
||||
notional = 15900000.DOLLARS,
|
||||
paymentFrequency = Frequency.SemiAnnual,
|
||||
effectiveDate = LocalDate.of(2016, 3, 16),
|
||||
effectiveDate = LocalDate.of(2016, 3, 10),
|
||||
effectiveDateAdjustment = null,
|
||||
terminationDate = LocalDate.of(2026, 3, 16),
|
||||
terminationDate = LocalDate.of(2026, 3, 10),
|
||||
terminationDateAdjustment = null,
|
||||
fixedRate = FixedRate(PercentageRatioUnit("1.677")),
|
||||
dayCountBasisDay = DayCountBasisDay.D30,
|
||||
@ -98,7 +99,6 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
|
||||
}
|
||||
2 -> {
|
||||
|
||||
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
|
||||
// I did a mock up start date 10/03/2015 – 10/03/2025 so you have 5 cashflows on float side that have been preset the rest are unknown
|
||||
|
||||
@ -181,7 +181,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
addressForTransfers = "",
|
||||
exposure = UnknownType(),
|
||||
localBusinessDay = BusinessCalendar.getInstance("London"),
|
||||
tradeID = "trade1",
|
||||
tradeID = "trade2",
|
||||
hashLegalDocs = "put hash here",
|
||||
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
||||
)
|
||||
@ -197,10 +197,25 @@ class IRSTests {
|
||||
|
||||
val attachments = MockStorageService().attachments
|
||||
|
||||
val exampleIRS = createDummyIRS(1)
|
||||
|
||||
val inState = InterestRateSwap.State(
|
||||
exampleIRS.fixedLeg,
|
||||
exampleIRS.floatingLeg,
|
||||
exampleIRS.calculation,
|
||||
exampleIRS.common
|
||||
)
|
||||
|
||||
val outState = inState.copy()
|
||||
|
||||
@Test
|
||||
fun ok() {
|
||||
val t = trade()
|
||||
t.verify()
|
||||
trade().verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ok with groups`() {
|
||||
tradegroups().verify()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,7 +258,7 @@ class IRSTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the generate
|
||||
* Test the generate. No explicit exception as if something goes wrong, we'll find out anyway.
|
||||
*/
|
||||
@Test
|
||||
fun generateIRS() {
|
||||
@ -251,6 +266,9 @@ class IRSTests {
|
||||
generateIRSTxn(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing a simple IRS, add a few fixings and then display as CSV
|
||||
*/
|
||||
@Test
|
||||
fun `IRS Export test`() {
|
||||
// No transactions etc required - we're just checking simple maths and export functionallity
|
||||
@ -351,7 +369,14 @@ class IRSTests {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a typical transactional history for an IRS.
|
||||
*/
|
||||
fun trade(): TransactionGroupDSL<InterestRateSwap.State> {
|
||||
|
||||
val ld = LocalDate.of(2016, 3, 8)
|
||||
val bd = BigDecimal("0.0063518")
|
||||
|
||||
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
@ -361,18 +386,349 @@ class IRSTests {
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement")
|
||||
output("irs post first fixing") { "irs post agreement".output }
|
||||
output("irs post first fixing") {
|
||||
"irs post agreement".output.copy(
|
||||
"irs post agreement".output.fixedLeg,
|
||||
"irs post agreement".output.floatingLeg,
|
||||
"irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||
"irs post agreement".output.common
|
||||
)
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
}
|
||||
return txgroup
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure failure occurs when there are inbound states for an agreement command`() {
|
||||
transaction {
|
||||
input() { singleIRS() }
|
||||
output("irs post agreement") { singleIRS() }
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are no in states for an agreement"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure failure occurs when no events in fix schedule`() {
|
||||
val irs = singleIRS()
|
||||
val emptySchedule = HashMap<LocalDate, FixedRatePaymentEvent>()
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are events in the fix schedule"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure failure occurs when no events in floating schedule`() {
|
||||
val irs = singleIRS()
|
||||
val emptySchedule = HashMap<LocalDate, FloatingRatePaymentEvent>()
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are events in the float schedule"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure notionals are non zero`() {
|
||||
val irs = singleIRS()
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(pennies = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All notionals must be non zero"
|
||||
}
|
||||
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(pennies = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All notionals must be non zero"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure positive rate on fixed leg`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1"))))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The fixed leg rate must be positive"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be modified once we adapt the IRS to be cross currency
|
||||
*/
|
||||
@Test
|
||||
fun `ensure same currency notionals`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.pennies, Currency.getInstance("JPY"))))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The currency of the notionals must be the same"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure notional amounts are equal`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.pennies + 1, irs.floatingLeg.notional.currency)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All leg notionals must be the same"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure trade date and termination date checks are done pt1`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS1
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective date is before the termination date for the fixed leg"
|
||||
}
|
||||
|
||||
val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS2
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective date is before the termination date for the floating leg"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure trade date and termination date checks are done pt2`() {
|
||||
val irs = singleIRS()
|
||||
|
||||
val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS3
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The termination dates are aligned"
|
||||
}
|
||||
|
||||
|
||||
val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS4
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective dates are aligned"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `various fixing tests`() {
|
||||
|
||||
val ld = LocalDate.of(2016, 3, 8)
|
||||
val bd = BigDecimal("0.0063518")
|
||||
|
||||
transaction {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
val oldIRS = singleIRS(1)
|
||||
val newIRS = oldIRS.copy(oldIRS.fixedLeg,
|
||||
oldIRS.floatingLeg,
|
||||
oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||
oldIRS.common)
|
||||
|
||||
transaction {
|
||||
input() {
|
||||
oldIRS
|
||||
|
||||
}
|
||||
|
||||
// Templated tweak for reference. A corrent fixing applied should be ok
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
output() { newIRS }
|
||||
this.accepts()
|
||||
}
|
||||
|
||||
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
output() { oldIRS }
|
||||
this`fails requirement` "There is at least one difference in the IRS floating leg payment schedules"
|
||||
}
|
||||
|
||||
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
|
||||
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
|
||||
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
|
||||
var modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.pennies, Currency.getInstance("JPY")))
|
||||
|
||||
output() {
|
||||
newIRS.copy(
|
||||
newIRS.fixedLeg,
|
||||
newIRS.floatingLeg,
|
||||
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
|
||||
Pair(firstResetKey, modifiedFirstResetValue))),
|
||||
newIRS.common
|
||||
)
|
||||
}
|
||||
this`fails requirement` "There is only one change in the IRS floating leg payment schedule"
|
||||
}
|
||||
|
||||
// This tests modifies the payment currency for the fixing
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
|
||||
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
|
||||
var modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.pennies, Currency.getInstance("JPY")))
|
||||
|
||||
output() {
|
||||
newIRS.copy(
|
||||
newIRS.fixedLeg,
|
||||
newIRS.floatingLeg,
|
||||
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
|
||||
Pair(latestReset.key, modifiedLatestResetValue))),
|
||||
newIRS.common
|
||||
)
|
||||
}
|
||||
this`fails requirement` "The fix payment has the same currency as the notional"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This returns an example of transactions that are grouped by TradeId and then a fixing applied.
|
||||
* It's important to make the tradeID different for two reasons, the hashes will be the same and all sorts of confusion will
|
||||
* result and the grouping won't work either.
|
||||
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
|
||||
*/
|
||||
fun tradegroups(): TransactionGroupDSL<InterestRateSwap.State> {
|
||||
val ld1 = LocalDate.of(2016, 3, 8)
|
||||
val bd1 = BigDecimal("0.0063518")
|
||||
|
||||
val irs = singleIRS()
|
||||
|
||||
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement1") {
|
||||
irs.copy(
|
||||
irs.fixedLeg,
|
||||
irs.floatingLeg,
|
||||
irs.calculation,
|
||||
irs.common.copy(tradeID = "t1")
|
||||
|
||||
)
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
transaction("Pay") {
|
||||
input("irs post first fixing")
|
||||
output("irs post first payment") { "irs post first fixing".output }
|
||||
arg(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { InterestRateSwap.Commands.Pay() }
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement2") {
|
||||
irs.copy(
|
||||
irs.fixedLeg,
|
||||
irs.floatingLeg,
|
||||
irs.calculation,
|
||||
irs.common.copy(tradeID = "t2")
|
||||
|
||||
)
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement1")
|
||||
input("irs post agreement2")
|
||||
output("irs post first fixing1") {
|
||||
"irs post agreement1".output.copy(
|
||||
"irs post agreement1".output.fixedLeg,
|
||||
"irs post agreement1".output.floatingLeg,
|
||||
"irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
"irs post agreement1".output.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
output("irs post first fixing2") {
|
||||
"irs post agreement2".output.copy(
|
||||
"irs post agreement2".output.fixedLeg,
|
||||
"irs post agreement2".output.floatingLeg,
|
||||
"irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
"irs post agreement2".output.common.copy(tradeID = "t2")
|
||||
)
|
||||
}
|
||||
|
||||
arg(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1)
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
}
|
||||
return txgroup
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -157,16 +157,16 @@ open class TransactionForTest : AbstractTransactionForTest() {
|
||||
private val inStates = arrayListOf<ContractState>()
|
||||
fun input(s: () -> ContractState) = inStates.add(s())
|
||||
|
||||
protected fun run(time: Instant) {
|
||||
protected fun runCommandsAndVerify(time: Instant) {
|
||||
val cmds = commandsToAuthenticatedObjects()
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.randomSHA256())
|
||||
tx.verify()
|
||||
}
|
||||
|
||||
fun accepts(time: Instant = TEST_TX_TIME) = run(time)
|
||||
fun accepts(time: Instant = TEST_TX_TIME) = runCommandsAndVerify(time)
|
||||
fun rejects(withMessage: String? = null, time: Instant = TEST_TX_TIME) {
|
||||
val r = try {
|
||||
run(time)
|
||||
runCommandsAndVerify(time)
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
val m = e.message
|
||||
@ -179,7 +179,9 @@ open class TransactionForTest : AbstractTransactionForTest() {
|
||||
if (!r) throw AssertionError("Expected exception but didn't get one")
|
||||
}
|
||||
|
||||
// which is uglier?? :)
|
||||
/**
|
||||
* Used to confirm that the test, when (implicitly) run against the .verify() method, fails with the text of the message
|
||||
*/
|
||||
infix fun `fails requirement`(msg: String) = rejects(msg)
|
||||
|
||||
fun fails_requirement(msg: String) = this.`fails requirement`(msg)
|
||||
|
Loading…
Reference in New Issue
Block a user