mirror of
https://github.com/corda/corda.git
synced 2025-01-31 08:25:50 +00:00
Removed IRS contract.
This commit is contained in:
parent
e3f5a96696
commit
a66d6b974e
@ -1,801 +0,0 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.node.services.ServiceType
|
||||
import com.r3corda.core.protocols.ProtocolLogicRefFactory
|
||||
import com.r3corda.core.transactions.TransactionBuilder
|
||||
import com.r3corda.core.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import com.r3corda.protocols.TwoPartyDealProtocol
|
||||
import org.apache.commons.jexl3.JexlBuilder
|
||||
import org.apache.commons.jexl3.MapContext
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.security.PublicKey
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
|
||||
val IRS_PROGRAM_ID = InterestRateSwap()
|
||||
|
||||
// This is a placeholder for some types that we haven't identified exactly what they are just yet for things still in discussion
|
||||
open class UnknownType() {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return (other is UnknownType)
|
||||
}
|
||||
|
||||
override fun hashCode() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Event superclass - everything happens on a date.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
abstract class PaymentEvent(date: LocalDate) : Event(date) {
|
||||
abstract fun calculate(): Amount<Currency>
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RatePaymentEvent] represents a dated obligation of payment.
|
||||
* It is a specialisation / modification of a basic cash flow event (to be written) that has some additional assistance
|
||||
* functions for interest rate swap legs of the fixed and floating nature.
|
||||
* For the fixed leg, the rate is already known at creation and therefore the flows can be pre-determined.
|
||||
* For the floating leg, the rate refers to a reference rate which is to be "fixed" at a point in the future.
|
||||
*/
|
||||
abstract class RatePaymentEvent(date: LocalDate,
|
||||
val accrualStartDate: LocalDate,
|
||||
val accrualEndDate: LocalDate,
|
||||
val dayCountBasisDay: DayCountBasisDay,
|
||||
val dayCountBasisYear: DayCountBasisYear,
|
||||
val notional: Amount<Currency>,
|
||||
val rate: Rate) : PaymentEvent(date) {
|
||||
companion object {
|
||||
val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow"
|
||||
}
|
||||
|
||||
override fun calculate(): Amount<Currency> = flow
|
||||
|
||||
abstract val flow: Amount<Currency>
|
||||
|
||||
val days: Int get() = calculateDaysBetween(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
|
||||
|
||||
// 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() = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.token},$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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic class for the Fixed Rate Payments on the fixed leg - see [RatePaymentEvent].
|
||||
* Assumes that the rate is valid.
|
||||
*/
|
||||
class FixedRatePaymentEvent(date: LocalDate,
|
||||
accrualStartDate: LocalDate,
|
||||
accrualEndDate: LocalDate,
|
||||
dayCountBasisDay: DayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear,
|
||||
notional: Amount<Currency>,
|
||||
rate: Rate) :
|
||||
RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
|
||||
companion object {
|
||||
val CSVHeader = RatePaymentEvent.CSVHeader
|
||||
}
|
||||
|
||||
override val flow: Amount<Currency> get() = Amount(dayCountFactor.times(BigDecimal(notional.quantity)).times(rate.ratioUnit!!.value).toLong(), notional.token)
|
||||
|
||||
override fun toString(): String =
|
||||
"FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow"
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic class for the Floating Rate Payments on the floating leg - see [RatePaymentEvent].
|
||||
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
|
||||
*/
|
||||
class FloatingRatePaymentEvent(date: LocalDate,
|
||||
accrualStartDate: LocalDate,
|
||||
accrualEndDate: LocalDate,
|
||||
dayCountBasisDay: DayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear,
|
||||
val fixingDate: LocalDate,
|
||||
notional: Amount<Currency>,
|
||||
rate: Rate) : RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
|
||||
|
||||
companion object {
|
||||
val CSVHeader = RatePaymentEvent.CSVHeader + ",FixingDate"
|
||||
}
|
||||
|
||||
override val flow: Amount<Currency> get() {
|
||||
// TODO: Should an uncalculated amount return a zero ? null ? etc.
|
||||
val v = rate.ratioUnit?.value ?: return Amount(0, notional.token)
|
||||
return Amount(dayCountFactor.times(BigDecimal(notional.quantity)).times(v).toLong(), notional.token)
|
||||
}
|
||||
|
||||
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.token},$notional,$fixingDate,$rate,$flow"
|
||||
|
||||
/**
|
||||
* Used for making immutables.
|
||||
*/
|
||||
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<Currency> = this.notional,
|
||||
rate: Rate = this.rate) = FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, fixingDate, notional, rate)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Interest Rate Swap class. For a quick overview of what an IRS is, see here - http://www.pimco.co.uk/EN/Education/Pages/InterestRateSwapsBasics1-08.aspx (no endorsement).
|
||||
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg".
|
||||
* It also has 4 commands, "Agree", "Fix", "Pay" and "Mature".
|
||||
* Currently, we are not interested (excuse pun) in valuing the swap, calculating the PVs, DFs and all that good stuff (soon though).
|
||||
* This is just a representation of a vanilla Fixed vs Floating (same currency) IRS in the R3 prototype model.
|
||||
*/
|
||||
class InterestRateSwap() : Contract {
|
||||
override val legalContractReference = SecureHash.sha256("is_this_the_text_of_the_contract ? TBD")
|
||||
|
||||
companion object {
|
||||
val oracleType = ServiceType.corda.getSubType("interest_rates")
|
||||
}
|
||||
|
||||
/**
|
||||
* This Common area contains all the information that is not leg specific.
|
||||
*/
|
||||
data class Common(
|
||||
val baseCurrency: Currency,
|
||||
val eligibleCurrency: Currency,
|
||||
val eligibleCreditSupport: String,
|
||||
val independentAmounts: Amount<Currency>,
|
||||
val threshold: Amount<Currency>,
|
||||
val minimumTransferAmount: Amount<Currency>,
|
||||
val rounding: Amount<Currency>,
|
||||
val valuationDateDescription: String, // This describes (in english) how regularly the swap is to be valued, e.g. "every local working day"
|
||||
val notificationTime: String,
|
||||
val resolutionTime: String,
|
||||
val interestRate: ReferenceRate,
|
||||
val addressForTransfers: String,
|
||||
val exposure: UnknownType,
|
||||
val localBusinessDay: BusinessCalendar,
|
||||
val dailyInterestAmount: Expression,
|
||||
val tradeID: String,
|
||||
val hashLegalDocs: String
|
||||
)
|
||||
|
||||
/**
|
||||
* The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains
|
||||
* data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually
|
||||
* copy / update for each transition).
|
||||
*/
|
||||
data class Calculation(
|
||||
val expression: Expression,
|
||||
val floatingLegPaymentSchedule: Map<LocalDate, FloatingRatePaymentEvent>,
|
||||
val fixedLegPaymentSchedule: Map<LocalDate, FixedRatePaymentEvent>
|
||||
) {
|
||||
/**
|
||||
* Gets the date of the next fixing.
|
||||
* @return LocalDate or null if no more fixings.
|
||||
*/
|
||||
fun nextFixingDate(): LocalDate? {
|
||||
return floatingLegPaymentSchedule.
|
||||
filter { it.value.rate is ReferenceRate }.// TODO - a better way to determine what fixings remain to be fixed
|
||||
minBy { it.value.fixingDate.toEpochDay() }?.value?.fixingDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fixing for that date.
|
||||
*/
|
||||
fun getFixing(date: LocalDate): FloatingRatePaymentEvent =
|
||||
floatingLegPaymentSchedule.values.single { it.fixingDate == date }
|
||||
|
||||
/**
|
||||
* Returns a copy after modifying (applying) the fixing for that date.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CommonLeg(
|
||||
val notional: Amount<Currency>,
|
||||
val paymentFrequency: Frequency,
|
||||
val effectiveDate: LocalDate,
|
||||
val effectiveDateAdjustment: DateRollConvention?,
|
||||
val terminationDate: LocalDate,
|
||||
val terminationDateAdjustment: DateRollConvention?,
|
||||
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," +
|
||||
"TerminationDateAdjustment=$terminationDateAdjustment,DayCountBasis=$dayCountBasisDay/$dayCountBasisYear,DayInMonth=$dayInMonth," +
|
||||
"PaymentRule=$paymentRule,PaymentDelay=$paymentDelay,PaymentCalendar=$paymentCalendar,InterestPeriodAdjustment=$interestPeriodAdjustment"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
||||
other as CommonLeg
|
||||
|
||||
if (notional != other.notional) return false
|
||||
if (paymentFrequency != other.paymentFrequency) return false
|
||||
if (effectiveDate != other.effectiveDate) return false
|
||||
if (effectiveDateAdjustment != other.effectiveDateAdjustment) return false
|
||||
if (terminationDate != other.terminationDate) return false
|
||||
if (terminationDateAdjustment != other.terminationDateAdjustment) return false
|
||||
if (dayCountBasisDay != other.dayCountBasisDay) return false
|
||||
if (dayCountBasisYear != other.dayCountBasisYear) return false
|
||||
if (dayInMonth != other.dayInMonth) return false
|
||||
if (paymentRule != other.paymentRule) return false
|
||||
if (paymentDelay != other.paymentDelay) return false
|
||||
if (paymentCalendar != other.paymentCalendar) return false
|
||||
if (interestPeriodAdjustment != other.interestPeriodAdjustment) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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(
|
||||
var fixedRatePayer: Party,
|
||||
notional: Amount<Currency>,
|
||||
paymentFrequency: Frequency,
|
||||
effectiveDate: LocalDate,
|
||||
effectiveDateAdjustment: DateRollConvention?,
|
||||
terminationDate: LocalDate,
|
||||
terminationDateAdjustment: DateRollConvention?,
|
||||
dayCountBasisDay: DayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear,
|
||||
dayInMonth: Int,
|
||||
paymentRule: PaymentRule,
|
||||
paymentDelay: Int,
|
||||
paymentCalendar: BusinessCalendar,
|
||||
interestPeriodAdjustment: AccrualAdjustment,
|
||||
var fixedRate: FixedRate,
|
||||
var rollConvention: DateRollConvention // TODO - best way of implementing - still awaiting some clarity
|
||||
) : CommonLeg
|
||||
(notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment) {
|
||||
override fun toString(): String = "FixedLeg(Payer=$fixedRatePayer," + super.toString() + ",fixedRate=$fixedRate," +
|
||||
"rollConvention=$rollConvention"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as FixedLeg
|
||||
|
||||
if (fixedRatePayer != other.fixedRatePayer) return false
|
||||
if (fixedRate != other.fixedRate) return false
|
||||
if (rollConvention != other.rollConvention) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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<Currency> = 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)
|
||||
|
||||
}
|
||||
|
||||
open class FloatingLeg(
|
||||
var floatingRatePayer: Party,
|
||||
notional: Amount<Currency>,
|
||||
paymentFrequency: Frequency,
|
||||
effectiveDate: LocalDate,
|
||||
effectiveDateAdjustment: DateRollConvention?,
|
||||
terminationDate: LocalDate,
|
||||
terminationDateAdjustment: DateRollConvention?,
|
||||
dayCountBasisDay: DayCountBasisDay,
|
||||
dayCountBasisYear: DayCountBasisYear,
|
||||
dayInMonth: Int,
|
||||
paymentRule: PaymentRule,
|
||||
paymentDelay: Int,
|
||||
paymentCalendar: BusinessCalendar,
|
||||
interestPeriodAdjustment: AccrualAdjustment,
|
||||
var rollConvention: DateRollConvention,
|
||||
var fixingRollConvention: DateRollConvention,
|
||||
var resetDayInMonth: Int,
|
||||
var fixingPeriodOffset: Int,
|
||||
var resetRule: PaymentRule,
|
||||
var fixingsPerPayment: Frequency,
|
||||
var fixingCalendar: BusinessCalendar,
|
||||
var index: String,
|
||||
var indexSource: String,
|
||||
var indexTenor: Tenor
|
||||
) : CommonLeg(notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment) {
|
||||
override fun toString(): String = "FloatingLeg(Payer=$floatingRatePayer," + super.toString() +
|
||||
"rollConvention=$rollConvention,FixingRollConvention=$fixingRollConvention,ResetDayInMonth=$resetDayInMonth" +
|
||||
"FixingPeriondOffset=$fixingPeriodOffset,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
|
||||
"Index=$index,IndexSource=$indexSource,IndexTenor=$indexTenor"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as FloatingLeg
|
||||
|
||||
if (floatingRatePayer != other.floatingRatePayer) return false
|
||||
if (rollConvention != other.rollConvention) return false
|
||||
if (fixingRollConvention != other.fixingRollConvention) return false
|
||||
if (resetDayInMonth != other.resetDayInMonth) return false
|
||||
if (fixingPeriodOffset != other.fixingPeriodOffset) return false
|
||||
if (resetRule != other.resetRule) return false
|
||||
if (fixingsPerPayment != other.fixingsPerPayment) return false
|
||||
if (fixingCalendar != other.fixingCalendar) return false
|
||||
if (index != other.index) return false
|
||||
if (indexSource != other.indexSource) return false
|
||||
if (indexTenor != other.indexTenor) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(floatingRatePayer, rollConvention,
|
||||
fixingRollConvention, resetDayInMonth, fixingPeriodOffset, resetRule, fixingsPerPayment, fixingCalendar,
|
||||
index, indexSource, indexTenor)
|
||||
|
||||
|
||||
fun copy(floatingRatePayer: Party = this.floatingRatePayer,
|
||||
notional: Amount<Currency> = 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: Int = this.fixingPeriodOffset,
|
||||
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)
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllComposition(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
|
||||
interface Clauses {
|
||||
/**
|
||||
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
||||
* helper functions for the clauses.
|
||||
*/
|
||||
abstract class AbstractIRSClause : Clause<State, Commands, UniqueIdentifier>() {
|
||||
// These functions may make more sense to use for basket types, but for now let's leave them here
|
||||
fun checkLegDates(legs: List<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: List<CommonLeg>) {
|
||||
requireThat {
|
||||
"The notional is non zero" by legs.any { it.notional.quantity > (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: List<CommonLeg>): Boolean = true
|
||||
|
||||
fun checkRates(@Suppress("UNUSED_PARAMETER") legs: List<CommonLeg>): Boolean = true
|
||||
|
||||
/**
|
||||
* 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] }
|
||||
return (diff1.keys + diff2.keys).map {
|
||||
it to Pair(diff1[it] as FloatingRatePaymentEvent, diff2[it] as FloatingRatePaymentEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Group : GroupClauseVerifier<State, Commands, UniqueIdentifier>(AnyComposition(Agree(), Fix(), Pay(), Mature())) {
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, UniqueIdentifier>>
|
||||
// Group by Trade ID for in / out states
|
||||
= tx.groupStates() { state -> state.linearId }
|
||||
}
|
||||
|
||||
class Timestamped : Clause<ContractState, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
// We return an empty set because we don't process any commands
|
||||
return emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
class Agree : AbstractIRSClause() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
||||
val irs = outputs.filterIsInstance<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.quantity > 0 && irs.floatingLeg.notional.quantity > 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.token == irs.floatingLeg.notional.token)
|
||||
"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(listOf(irs.fixedLeg, irs.floatingLeg))
|
||||
"The schedules are valid" by checkSchedules(listOf(irs.fixedLeg, irs.floatingLeg))
|
||||
"The fixing period date offset cannot be negative" by (irs.floatingLeg.fixingPeriodOffset >= 0)
|
||||
|
||||
// TODO: further tests
|
||||
}
|
||||
checkLegAmounts(listOf(irs.fixedLeg, irs.floatingLeg))
|
||||
checkLegDates(listOf(irs.fixedLeg, irs.floatingLeg))
|
||||
|
||||
return setOf(command.value)
|
||||
}
|
||||
}
|
||||
|
||||
class Fix : AbstractIRSClause() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
||||
val irs = outputs.filterIsInstance<State>().single()
|
||||
val prevIrs = inputs.filterIsInstance<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 fixValue = command.value.fix
|
||||
// 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.token == irs.floatingLeg.notional.token)
|
||||
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
|
||||
}
|
||||
|
||||
return setOf(command.value)
|
||||
}
|
||||
}
|
||||
|
||||
class Pay : AbstractIRSClause() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
||||
requireThat {
|
||||
"Payments not supported / verifiable yet" by false
|
||||
}
|
||||
return setOf(command.value)
|
||||
}
|
||||
}
|
||||
|
||||
class Mature : AbstractIRSClause() {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
||||
val irs = inputs.filterIsInstance<State>().single()
|
||||
requireThat {
|
||||
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
||||
"The irs is fully consumed and there is no id matched output state" by outputs.isEmpty()
|
||||
}
|
||||
|
||||
return setOf(command.value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
data class Refix(val fix: Fix) : Commands // Receive interest rate from oracle, Both sides agree
|
||||
class Pay : TypeOnlyCommandData(), Commands // Not implemented just yet
|
||||
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade
|
||||
class Mature : TypeOnlyCommandData(), Commands // Trade has matured; no more actions. Cleanup. // TODO: Do we need this?
|
||||
}
|
||||
|
||||
/**
|
||||
* The state class contains the 4 major data classes.
|
||||
*/
|
||||
data class State(
|
||||
val fixedLeg: FixedLeg,
|
||||
val floatingLeg: FloatingLeg,
|
||||
val calculation: Calculation,
|
||||
val common: Common,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
||||
) : FixableDealState, SchedulableState {
|
||||
|
||||
override val contract = IRS_PROGRAM_ID
|
||||
|
||||
override val oracleType: ServiceType
|
||||
get() = InterestRateSwap.oracleType
|
||||
|
||||
override val ref = common.tradeID
|
||||
|
||||
override val participants: List<PublicKey>
|
||||
get() = parties.map { it.owningKey }
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys)
|
||||
}
|
||||
|
||||
override val parties: List<Party>
|
||||
get() = listOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
|
||||
// This is perhaps not how we should determine the time point in the business day, but instead expect the schedule to detail some of these aspects
|
||||
val instant = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay).start
|
||||
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||
}
|
||||
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
||||
|
||||
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
||||
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), fix)
|
||||
}
|
||||
|
||||
override fun nextFixingOf(): FixOf? {
|
||||
val date = calculation.nextFixingDate()
|
||||
return if (date == null) null else {
|
||||
val fixingEvent = calculation.getFixing(date)
|
||||
val oracleRate = fixingEvent.rate as ReferenceRate
|
||||
FixOf(oracleRate.name, date, oracleRate.tenor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For evaluating arbitrary java on the platform.
|
||||
*/
|
||||
|
||||
fun evaluateCalculation(businessDate: LocalDate, expression: Expression = calculation.expression): Any {
|
||||
// TODO: Jexl is purely for prototyping. It may be replaced
|
||||
// TODO: Whatever we do use must be secure and sandboxed
|
||||
val jexl = JexlBuilder().create()
|
||||
val expr = jexl.createExpression(expression.expr)
|
||||
val jc = MapContext()
|
||||
jc.set("fixedLeg", fixedLeg)
|
||||
jc.set("floatingLeg", floatingLeg)
|
||||
jc.set("calculation", calculation)
|
||||
jc.set("common", common)
|
||||
jc.set("currentBusinessDate", businessDate)
|
||||
return expr.evaluate(jc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Just makes printing it out a bit better for those who don't have 80000 column wide monitors.
|
||||
*/
|
||||
fun prettyPrint() = toString().replace(",", "\n")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This generates the agreement state and also the schedules from the initial data.
|
||||
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
|
||||
*/
|
||||
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation,
|
||||
common: Common, notary: Party): TransactionBuilder {
|
||||
|
||||
val fixedLegPaymentSchedule = HashMap<LocalDate, FixedRatePaymentEvent>()
|
||||
var dates = BusinessCalendar.createGenericSchedule(fixedLeg.effectiveDate, fixedLeg.paymentFrequency, fixedLeg.paymentCalendar, fixedLeg.rollConvention, endDate = fixedLeg.terminationDate)
|
||||
var periodStartDate = fixedLeg.effectiveDate
|
||||
|
||||
// Create a schedule for the fixed payments
|
||||
for (periodEndDate in dates) {
|
||||
val paymentDate = BusinessCalendar.getOffsetDate(periodEndDate, Frequency.Daily, fixedLeg.paymentDelay)
|
||||
val paymentEvent = FixedRatePaymentEvent(
|
||||
paymentDate,
|
||||
periodStartDate,
|
||||
periodEndDate,
|
||||
fixedLeg.dayCountBasisDay,
|
||||
fixedLeg.dayCountBasisYear,
|
||||
fixedLeg.notional,
|
||||
fixedLeg.fixedRate
|
||||
)
|
||||
fixedLegPaymentSchedule[paymentDate] = paymentEvent
|
||||
periodStartDate = periodEndDate
|
||||
}
|
||||
|
||||
dates = BusinessCalendar.createGenericSchedule(floatingLeg.effectiveDate,
|
||||
floatingLeg.fixingsPerPayment,
|
||||
floatingLeg.fixingCalendar,
|
||||
floatingLeg.rollConvention,
|
||||
endDate = floatingLeg.terminationDate)
|
||||
|
||||
val floatingLegPaymentSchedule: MutableMap<LocalDate, FloatingRatePaymentEvent> = HashMap()
|
||||
periodStartDate = floatingLeg.effectiveDate
|
||||
|
||||
// Now create a schedule for the floating and fixes.
|
||||
for (periodEndDate in dates) {
|
||||
val paymentDate = BusinessCalendar.getOffsetDate(periodEndDate, Frequency.Daily, floatingLeg.paymentDelay)
|
||||
val paymentEvent = FloatingRatePaymentEvent(
|
||||
paymentDate,
|
||||
periodStartDate,
|
||||
periodEndDate,
|
||||
floatingLeg.dayCountBasisDay,
|
||||
floatingLeg.dayCountBasisYear,
|
||||
calcFixingDate(periodStartDate, floatingLeg.fixingPeriodOffset, floatingLeg.fixingCalendar),
|
||||
floatingLeg.notional,
|
||||
ReferenceRate(floatingLeg.indexSource, floatingLeg.indexTenor, floatingLeg.index)
|
||||
)
|
||||
|
||||
floatingLegPaymentSchedule[paymentDate] = paymentEvent
|
||||
periodStartDate = periodEndDate
|
||||
}
|
||||
|
||||
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
|
||||
|
||||
// Put all the above into a new State object.
|
||||
val state = State(fixedLeg, floatingLeg, newCalculation, common)
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
|
||||
}
|
||||
|
||||
private fun calcFixingDate(date: LocalDate, fixingPeriodOffset: Int, calendar: BusinessCalendar): LocalDate {
|
||||
return when (fixingPeriodOffset) {
|
||||
0 -> date
|
||||
else -> calendar.moveBusinessDays(date, DateRollDirection.BACKWARD, fixingPeriodOffset)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Fix) {
|
||||
tx.addInputState(irs)
|
||||
val fixedRate = FixedRate(RatioUnit(fixing.value))
|
||||
tx.addOutputState(
|
||||
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.of.forDay, fixedRate)),
|
||||
irs.state.notary
|
||||
)
|
||||
tx.addCommand(Commands.Refix(fixing), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
fun InterestRateSwap.State.exportIRSToCSV(): String =
|
||||
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
|
||||
this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n" +
|
||||
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +
|
||||
this.calculation.floatingLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n"
|
@ -1,88 +0,0 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.contracts.Tenor
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
||||
|
||||
// Things in here will move to the general utils class when we've hammered out various discussions regarding amounts, dates, oracle etc.
|
||||
|
||||
/**
|
||||
* A utility class to prevent the various mixups between percentages, decimals, bips etc.
|
||||
*/
|
||||
open class RatioUnit(val value: BigDecimal) { // TODO: Discuss this type
|
||||
override fun equals(other: Any?) = (other as? RatioUnit)?.value == value
|
||||
override fun hashCode() = value.hashCode()
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to reprecent a percentage in an unambiguous way.
|
||||
*/
|
||||
open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecimal(percentageAsString).divide(BigDecimal("100"))) {
|
||||
override fun toString() = value.times(BigDecimal(100)).toString() + "%"
|
||||
}
|
||||
|
||||
/**
|
||||
* For the convenience of writing "5".percent
|
||||
* Note that we do not currently allow 10.percent (ie no quotes) as this might get a little confusing if 0.1.percent was
|
||||
* written. Additionally, there is a possibility of creating a precision error in the implicit conversion.
|
||||
*/
|
||||
val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)
|
||||
|
||||
/**
|
||||
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc.
|
||||
*/
|
||||
open class Rate(val ratioUnit: RatioUnit? = null) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
||||
other as Rate
|
||||
|
||||
if (ratioUnit != other.ratioUnit) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the hash code of the ratioUnit or zero if the ratioUnit is null, as is the case for floating rate fixings
|
||||
* 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() = ratioUnit?.hashCode() ?: 0
|
||||
override fun toString() = ratioUnit.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A very basic subclass to represent a fixed rate.
|
||||
*/
|
||||
class FixedRate(ratioUnit: RatioUnit) : Rate(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()
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent class of the Floating rate classes.
|
||||
*/
|
||||
open class FloatingRate : Rate(null)
|
||||
|
||||
/**
|
||||
* So a reference rate is a rate that takes its value from a source at a given date
|
||||
* e.g. LIBOR 6M as of 17 March 2016. Hence it requires a source (name) and a value date in the getAsOf(..) method.
|
||||
*/
|
||||
class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : FloatingRate() {
|
||||
override fun toString(): String = "$name - $tenor"
|
||||
}
|
||||
|
||||
// TODO: For further discussion.
|
||||
operator fun Amount<Currency>.times(other: RatioUnit): Amount<Currency> = Amount((BigDecimal(this.quantity).multiply(other.value)).longValueExact(), this.token)
|
||||
//operator fun Amount<Currency>.times(other: FixedRate): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
|
||||
//fun Amount<Currency>.times(other: InterestRateSwap.RatioUnit): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
|
||||
|
||||
operator fun kotlin.Int.times(other: FixedRate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact()
|
||||
operator fun Int.times(other: Rate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact()
|
||||
operator fun Int.times(other: RatioUnit): Int = BigDecimal(this).multiply(other.value).intValueExact()
|
@ -1,718 +0,0 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.node.recordTransactions
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.transactions.SignedTransaction
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import com.r3corda.core.utilities.TEST_TX_TIME
|
||||
import com.r3corda.testing.*
|
||||
import com.r3corda.testing.node.MockServices
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
|
||||
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
return when (irsSelect) {
|
||||
1 -> {
|
||||
|
||||
val fixedLeg = InterestRateSwap.FixedLeg(
|
||||
fixedRatePayer = MEGA_CORP,
|
||||
notional = 15900000.DOLLARS,
|
||||
paymentFrequency = Frequency.SemiAnnual,
|
||||
effectiveDate = LocalDate.of(2016, 3, 10),
|
||||
effectiveDateAdjustment = null,
|
||||
terminationDate = LocalDate.of(2026, 3, 10),
|
||||
terminationDateAdjustment = null,
|
||||
fixedRate = FixedRate(PercentageRatioUnit("1.677")),
|
||||
dayCountBasisDay = DayCountBasisDay.D30,
|
||||
dayCountBasisYear = DayCountBasisYear.Y360,
|
||||
rollConvention = DateRollConvention.ModifiedFollowing,
|
||||
dayInMonth = 10,
|
||||
paymentRule = PaymentRule.InArrears,
|
||||
paymentDelay = 3,
|
||||
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
|
||||
interestPeriodAdjustment = AccrualAdjustment.Adjusted
|
||||
)
|
||||
|
||||
val floatingLeg = InterestRateSwap.FloatingLeg(
|
||||
floatingRatePayer = MINI_CORP,
|
||||
notional = 15900000.DOLLARS,
|
||||
paymentFrequency = Frequency.Quarterly,
|
||||
effectiveDate = LocalDate.of(2016, 3, 10),
|
||||
effectiveDateAdjustment = null,
|
||||
terminationDate = LocalDate.of(2026, 3, 10),
|
||||
terminationDateAdjustment = null,
|
||||
dayCountBasisDay = DayCountBasisDay.D30,
|
||||
dayCountBasisYear = DayCountBasisYear.Y360,
|
||||
rollConvention = DateRollConvention.ModifiedFollowing,
|
||||
fixingRollConvention = DateRollConvention.ModifiedFollowing,
|
||||
dayInMonth = 10,
|
||||
resetDayInMonth = 10,
|
||||
paymentRule = PaymentRule.InArrears,
|
||||
paymentDelay = 3,
|
||||
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
|
||||
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
|
||||
fixingPeriodOffset = 2,
|
||||
resetRule = PaymentRule.InAdvance,
|
||||
fixingsPerPayment = Frequency.Quarterly,
|
||||
fixingCalendar = BusinessCalendar.getInstance("London"),
|
||||
index = "LIBOR",
|
||||
indexSource = "TEL3750",
|
||||
indexTenor = Tenor("3M")
|
||||
)
|
||||
|
||||
val calculation = InterestRateSwap.Calculation (
|
||||
|
||||
// TODO: this seems to fail quite dramatically
|
||||
//expression = "fixedLeg.notional * fixedLeg.fixedRate",
|
||||
|
||||
// TODO: How I want it to look
|
||||
//expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))",
|
||||
|
||||
// How it's ended up looking, which I think is now broken but it's a WIP.
|
||||
expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
|
||||
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
|
||||
|
||||
floatingLegPaymentSchedule = HashMap(),
|
||||
fixedLegPaymentSchedule = HashMap()
|
||||
)
|
||||
|
||||
val EUR = currency("EUR")
|
||||
|
||||
val common = InterestRateSwap.Common(
|
||||
baseCurrency = EUR,
|
||||
eligibleCurrency = EUR,
|
||||
eligibleCreditSupport = "Cash in an Eligible Currency",
|
||||
independentAmounts = Amount(0, EUR),
|
||||
threshold = Amount(0, EUR),
|
||||
minimumTransferAmount = Amount(250000 * 100, EUR),
|
||||
rounding = Amount(10000 * 100, EUR),
|
||||
valuationDateDescription = "Every Local Business Day",
|
||||
notificationTime = "2:00pm London",
|
||||
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
|
||||
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
|
||||
addressForTransfers = "",
|
||||
exposure = UnknownType(),
|
||||
localBusinessDay = BusinessCalendar.getInstance("London"),
|
||||
tradeID = "trade1",
|
||||
hashLegalDocs = "put hash here",
|
||||
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
val fixedLeg = InterestRateSwap.FixedLeg(
|
||||
fixedRatePayer = MEGA_CORP,
|
||||
notional = 25000000.DOLLARS,
|
||||
paymentFrequency = Frequency.SemiAnnual,
|
||||
effectiveDate = LocalDate.of(2015, 3, 10),
|
||||
effectiveDateAdjustment = null,
|
||||
terminationDate = LocalDate.of(2025, 3, 10),
|
||||
terminationDateAdjustment = null,
|
||||
fixedRate = FixedRate(PercentageRatioUnit("1.3")),
|
||||
dayCountBasisDay = DayCountBasisDay.D30,
|
||||
dayCountBasisYear = DayCountBasisYear.Y360,
|
||||
rollConvention = DateRollConvention.ModifiedFollowing,
|
||||
dayInMonth = 10,
|
||||
paymentRule = PaymentRule.InArrears,
|
||||
paymentDelay = 0,
|
||||
paymentCalendar = BusinessCalendar.getInstance(),
|
||||
interestPeriodAdjustment = AccrualAdjustment.Adjusted
|
||||
)
|
||||
|
||||
val floatingLeg = InterestRateSwap.FloatingLeg(
|
||||
floatingRatePayer = MINI_CORP,
|
||||
notional = 25000000.DOLLARS,
|
||||
paymentFrequency = Frequency.Quarterly,
|
||||
effectiveDate = LocalDate.of(2015, 3, 10),
|
||||
effectiveDateAdjustment = null,
|
||||
terminationDate = LocalDate.of(2025, 3, 10),
|
||||
terminationDateAdjustment = null,
|
||||
dayCountBasisDay = DayCountBasisDay.DActual,
|
||||
dayCountBasisYear = DayCountBasisYear.Y360,
|
||||
rollConvention = DateRollConvention.ModifiedFollowing,
|
||||
fixingRollConvention = DateRollConvention.ModifiedFollowing,
|
||||
dayInMonth = 10,
|
||||
resetDayInMonth = 10,
|
||||
paymentRule = PaymentRule.InArrears,
|
||||
paymentDelay = 0,
|
||||
paymentCalendar = BusinessCalendar.getInstance(),
|
||||
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
|
||||
fixingPeriodOffset = 2,
|
||||
resetRule = PaymentRule.InAdvance,
|
||||
fixingsPerPayment = Frequency.Quarterly,
|
||||
fixingCalendar = BusinessCalendar.getInstance(),
|
||||
index = "USD LIBOR",
|
||||
indexSource = "TEL3750",
|
||||
indexTenor = Tenor("3M")
|
||||
)
|
||||
|
||||
val calculation = InterestRateSwap.Calculation (
|
||||
|
||||
// TODO: this seems to fail quite dramatically
|
||||
//expression = "fixedLeg.notional * fixedLeg.fixedRate",
|
||||
|
||||
// TODO: How I want it to look
|
||||
//expression = "( fixedLeg.notional * (fixedLeg.fixedRate)) - (floatingLeg.notional * (rateSchedule.get(context.getDate('currentDate'))))",
|
||||
|
||||
// How it's ended up looking, which I think is now broken but it's a WIP.
|
||||
expression = Expression("( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -" +
|
||||
"(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))"),
|
||||
|
||||
floatingLegPaymentSchedule = HashMap(),
|
||||
fixedLegPaymentSchedule = HashMap()
|
||||
)
|
||||
|
||||
val EUR = currency("EUR")
|
||||
|
||||
val common = InterestRateSwap.Common(
|
||||
baseCurrency = EUR,
|
||||
eligibleCurrency = EUR,
|
||||
eligibleCreditSupport = "Cash in an Eligible Currency",
|
||||
independentAmounts = Amount(0, EUR),
|
||||
threshold = Amount(0, EUR),
|
||||
minimumTransferAmount = Amount(250000 * 100, EUR),
|
||||
rounding = Amount(10000 * 100, EUR),
|
||||
valuationDateDescription = "Every Local Business Day",
|
||||
notificationTime = "2:00pm London",
|
||||
resolutionTime = "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
|
||||
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
|
||||
addressForTransfers = "",
|
||||
exposure = UnknownType(),
|
||||
localBusinessDay = BusinessCalendar.getInstance("London"),
|
||||
tradeID = "trade2",
|
||||
hashLegalDocs = "put hash here",
|
||||
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
||||
)
|
||||
|
||||
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
|
||||
|
||||
}
|
||||
else -> TODO("IRS number $irsSelect not defined")
|
||||
}
|
||||
}
|
||||
|
||||
class IRSTests {
|
||||
@Test
|
||||
fun ok() {
|
||||
trade().verifies()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ok with groups`() {
|
||||
tradegroups().verifies()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an IRS txn - we'll need it for a few things.
|
||||
*/
|
||||
fun generateIRSTxn(irsSelect: Int): SignedTransaction {
|
||||
val dummyIRS = createDummyIRS(irsSelect)
|
||||
val genTX: SignedTransaction = run {
|
||||
val gtx = InterestRateSwap().generateAgreement(
|
||||
fixedLeg = dummyIRS.fixedLeg,
|
||||
floatingLeg = dummyIRS.floatingLeg,
|
||||
calculation = dummyIRS.calculation,
|
||||
common = dummyIRS.common,
|
||||
notary = DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}
|
||||
gtx.toSignedTransaction()
|
||||
}
|
||||
return genTX
|
||||
}
|
||||
|
||||
/**
|
||||
* Just make sure it's sane.
|
||||
*/
|
||||
@Test
|
||||
fun pprintIRS() {
|
||||
val irs = singleIRS()
|
||||
println(irs.prettyPrint())
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility so I don't have to keep typing this.
|
||||
*/
|
||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
||||
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the generate. No explicit exception as if something goes wrong, we'll find out anyway.
|
||||
*/
|
||||
@Test
|
||||
fun generateIRS() {
|
||||
// Tests aren't allowed to return things
|
||||
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
|
||||
val irs = singleIRS(2)
|
||||
|
||||
var newCalculation = irs.calculation
|
||||
|
||||
val fixings = mapOf(LocalDate.of(2015, 3, 6) to "0.6",
|
||||
LocalDate.of(2015, 6, 8) to "0.75",
|
||||
LocalDate.of(2015, 9, 8) to "0.8",
|
||||
LocalDate.of(2015, 12, 8) to "0.55",
|
||||
LocalDate.of(2016, 3, 8) to "0.644")
|
||||
|
||||
for ((key, value) in fixings) {
|
||||
newCalculation = newCalculation.applyFixing(key, FixedRate(PercentageRatioUnit(value)))
|
||||
}
|
||||
|
||||
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
|
||||
println(newIRS.exportIRSToCSV())
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure it has a schedule and the schedule has some unfixed rates.
|
||||
*/
|
||||
@Test
|
||||
fun `next fixing date`() {
|
||||
val irs = singleIRS(1)
|
||||
println(irs.calculation.nextFixingDate())
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through all the fix dates and add something.
|
||||
*/
|
||||
@Test
|
||||
fun generateIRSandFixSome() {
|
||||
val services = MockServices()
|
||||
var previousTXN = generateIRSTxn(1)
|
||||
previousTXN.toLedgerTransaction(services).verify()
|
||||
services.recordTransactions(previousTXN)
|
||||
fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||
|
||||
while (true) {
|
||||
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
|
||||
val fixTX: SignedTransaction = run {
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
val fixing = Fix(nextFix, "0.052".percent.value)
|
||||
InterestRateSwap().generateFix(tx, previousTXN.tx.outRef(0), fixing)
|
||||
with(tx) {
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}
|
||||
tx.toSignedTransaction()
|
||||
}
|
||||
fixTX.toLedgerTransaction(services).verify()
|
||||
services.recordTransactions(fixTX)
|
||||
previousTXN = fixTX
|
||||
}
|
||||
}
|
||||
|
||||
// Move these later as they aren't IRS specific.
|
||||
@Test
|
||||
fun `test some rate objects 100 * FixedRate(5%)`() {
|
||||
val r1 = FixedRate(PercentageRatioUnit("5"))
|
||||
assert(100 * r1 == 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `expression calculation testing`() {
|
||||
val dummyIRS = singleIRS()
|
||||
val stuffToPrint: ArrayList<String> = arrayListOf(
|
||||
"fixedLeg.notional.quantity",
|
||||
"fixedLeg.fixedRate.ratioUnit",
|
||||
"fixedLeg.fixedRate.ratioUnit.value",
|
||||
"floatingLeg.notional.quantity",
|
||||
"fixedLeg.fixedRate",
|
||||
"currentBusinessDate",
|
||||
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
|
||||
"fixedLeg.notional.token.currencyCode",
|
||||
"fixedLeg.notional.quantity * 10",
|
||||
"fixedLeg.notional.quantity * fixedLeg.fixedRate.ratioUnit.value",
|
||||
"(fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360 ",
|
||||
"(fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value))"
|
||||
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate"
|
||||
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value",
|
||||
//"( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
// "( fixedLeg.notional * fixedLeg.fixedRate )"
|
||||
)
|
||||
|
||||
for (i in stuffToPrint) {
|
||||
println(i)
|
||||
val z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 15), Expression(i))
|
||||
println(z.javaClass)
|
||||
println(z)
|
||||
println("-----------")
|
||||
}
|
||||
// This does not throw an exception in the test itself; it evaluates the above and they will throw if they do not pass.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a typical transactional history for an IRS.
|
||||
*/
|
||||
fun trade(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
|
||||
val ld = LocalDate.of(2016, 3, 8)
|
||||
val bd = BigDecimal("0.0063518")
|
||||
|
||||
return ledger {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement")
|
||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing") {
|
||||
postAgreement.copy(
|
||||
postAgreement.fixedLeg,
|
||||
postAgreement.floatingLeg,
|
||||
postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||
postAgreement.common
|
||||
)
|
||||
}
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure failure occurs when there are inbound states for an agreement command`() {
|
||||
val irs = singleIRS()
|
||||
transaction {
|
||||
input() { irs }
|
||||
output("irs post agreement") { irs }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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(quantity = 0)))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "All notionals must be non zero"
|
||||
}
|
||||
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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.quantity, Currency.getInstance("JPY"))))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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.quantity + 1, irs.floatingLeg.notional.token)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "The termination dates are aligned"
|
||||
}
|
||||
|
||||
|
||||
val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS4
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "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() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
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 {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
output() { newIRS }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
|
||||
tweak {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
output() { oldIRS }
|
||||
this `fails with` "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 {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
|
||||
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
|
||||
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
|
||||
val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, 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 with` "There is only one change in the IRS floating leg payment schedule"
|
||||
}
|
||||
|
||||
// This tests modifies the payment currency for the fixing
|
||||
tweak {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
|
||||
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
|
||||
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, 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 with` "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(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
val ld1 = LocalDate.of(2016, 3, 8)
|
||||
val bd1 = BigDecimal("0.0063518")
|
||||
|
||||
val irs = singleIRS()
|
||||
|
||||
return ledger {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement1") {
|
||||
irs.copy(
|
||||
irs.fixedLeg,
|
||||
irs.floatingLeg,
|
||||
irs.calculation,
|
||||
irs.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement2") {
|
||||
irs.copy(
|
||||
linearId = UniqueIdentifier("t2"),
|
||||
fixedLeg = irs.fixedLeg,
|
||||
floatingLeg = irs.floatingLeg,
|
||||
calculation = irs.calculation,
|
||||
common = irs.common.copy(tradeID = "t2")
|
||||
)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement1")
|
||||
input("irs post agreement2")
|
||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing1") {
|
||||
postAgreement1.copy(
|
||||
postAgreement1.fixedLeg,
|
||||
postAgreement1.floatingLeg,
|
||||
postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
postAgreement1.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing2") {
|
||||
postAgreement2.copy(
|
||||
postAgreement2.fixedLeg,
|
||||
postAgreement2.floatingLeg,
|
||||
postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
postAgreement2.common.copy(tradeID = "t2")
|
||||
)
|
||||
}
|
||||
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user