IRS Contract example

This commit is contained in:
Richard Green 2016-03-17 14:01:10 +00:00
parent 80af936a82
commit a78c5b11d1
8 changed files with 1061 additions and 9 deletions
contracts/src/main/kotlin/contracts
core/src
main/kotlin/core
test/kotlin/core
src/test/kotlin
contracts
core/testutils

@ -0,0 +1,453 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package contracts
import core.*
import core.crypto.SecureHash
import core.node.services.DummyTimestampingAuthority
import org.apache.commons.jexl3.JexlBuilder
import org.apache.commons.jexl3.MapContext
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.LocalDate
import java.util.*
val IRS_PROGRAM_ID = SecureHash.sha256("replace-me-later-with-bytecode-hash-of-irs-code")
// 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()
/**
* Event superclass - everything happens on a date.
*/
open class Event(val date: LocalDate)
/**
* 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
}
/**
* 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,
val rate: Rate) : PaymentEvent(date) {
companion object {
val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow"
}
override fun calculate(): Amount = flow
abstract val flow: Amount
val days: Int get() =
dayCountCalculator(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)
open fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow"
}
/**
* 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,
rate: Rate) :
RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
companion object {
val CSVHeader = RatePaymentEvent.CSVHeader
}
override val flow: Amount get() =
Amount(dayCountFactor.times(BigDecimal(notional.pennies)).times(rate.ratioUnit!!.value).toLong(), notional.currency)
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,
rate: Rate) : RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
companion object {
val CSVHeader = RatePaymentEvent.CSVHeader + ",FixingDate"
}
override val flow: Amount get() {
// TODO: Should an uncalculated amount return a zero ? null ? etc.
val v = rate.ratioUnit?.value ?: return Amount(0, notional.currency)
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 asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$fixingDate,$rate,$flow"
/**
* Used for making immutables
*/
fun withNewRate(newRate: Rate): FloatingRatePaymentEvent =
FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay,
dayCountBasisYear, fixingDate, notional, newRate)
}
/**
* Don't try and use a rate that isn't ready yet.
*/
class DataNotReadyException : Exception()
/**
* 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 = SecureHash.sha256("is_this_the_text_of_the_contract ? TBD")
/**
* 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,
val threshold: Amount,
val minimumTransferAmount: Amount,
val rounding: Amount,
val valuationDate: String,
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
)
data class Expression(val expr: 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 OracleRetrievableReferenceRate }.// 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: Rate): 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(
val notional: Amount,
val paymentFrequency: Frequency,
val effectiveDate: LocalDate,
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
) {
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"
}
}
open class FixedLeg(
var fixedRatePayer: Party,
notional: Amount,
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"
}
open class FloatingLeg(
var floatingRatePayer: Party,
notional: Amount,
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 fixingPeriod: DateOffset,
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" +
"FixingPeriond=$fixingPeriod,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
"Index=$index,IndexSource=$indexSource,IndexTenor=$indexTenor"
}
/**
* verify() with a few examples of what needs to be checked. TODO: Lots more to add.
*/
override fun verify(tx: TransactionForVerification) {
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
val time = tx.getTimestampBy(DummyTimestampingAuthority.identity)?.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
}
}
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
}
}
else -> throw IllegalArgumentException("Unrecognised verifiable command: ${command.value}")
}
}
interface Commands : CommandData {
class Fix : TypeOnlyCommandData(), 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
) : ContractState {
override val programRef = IRS_PROGRAM_ID
/**
* 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
var jexl = JexlBuilder().create()
var expr = jexl.createExpression(expression.expr)
var 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(): String = 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): 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 paymentEvent = FixedRatePaymentEvent(
// TODO: We are assuming the payment date is the end date of the accrual period.
periodEndDate, periodStartDate, periodEndDate,
fixedLeg.dayCountBasisDay,
fixedLeg.dayCountBasisYear,
fixedLeg.notional,
fixedLeg.fixedRate
)
fixedLegPaymentSchedule[periodEndDate] = paymentEvent
periodStartDate = periodEndDate
}
dates = BusinessCalendar.createGenericSchedule(floatingLeg.effectiveDate,
floatingLeg.fixingsPerPayment,
floatingLeg.fixingCalendar,
floatingLeg.rollConvention,
endDate = floatingLeg.terminationDate)
var floatingLegPaymentSchedule: MutableMap<LocalDate, FloatingRatePaymentEvent> = HashMap()
periodStartDate = floatingLeg.effectiveDate
// TODO: Temporary until implemented via Rates Oracle.
val telerate = TelerateOracle("3750")
// Now create a schedule for the floating and fixes.
for (periodEndDate in dates) {
val paymentEvent = FloatingRatePaymentEvent(
periodEndDate,
periodStartDate,
periodEndDate,
floatingLeg.dayCountBasisDay,
floatingLeg.dayCountBasisYear,
calcFixingDate(periodStartDate, floatingLeg.fixingPeriod, floatingLeg.fixingCalendar),
floatingLeg.notional,
// TODO: OracleRetrievableReferenceRate will be replaced via oracle v soon.
OracleRetrievableReferenceRate(telerate, floatingLeg.indexTenor, floatingLeg.index)
)
floatingLegPaymentSchedule.put(periodEndDate, 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 TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
}
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
return when (fixingPeriod) {
DateOffset.ZERO -> date
DateOffset.TWODAYS -> calendar.moveBusinessDays(date, DateRollDirection.BACKWARD, 2)
else -> TODO("Improved fixing date calculation logic")
}
}
// 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.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey))
}
}

@ -0,0 +1,15 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package 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"

@ -0,0 +1,100 @@
package contracts
import core.Amount
import core.Tenor
import java.math.BigDecimal
import java.time.LocalDate
// 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(value: BigDecimal) { // TODO: Discuss this type
val value = value
}
/**
* 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()+"%"
}
/**
* 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 TODO: Discuss
*/
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)
/**
* A very basic subclass to represent a fixed rate.
*/
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
override fun toString(): String = "$ratioUnit"
}
/**
* 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.
*/
abstract class ReferenceRate(val name: String): FloatingRate() {
abstract fun getAsOf(date: LocalDate?) : RatioUnit
}
/**
* A concrete implementation of the above for testing purposes
*/
open class TestReferenceRate(val testrate: String) : ReferenceRate(testrate) {
override fun getAsOf(date: LocalDate?) : RatioUnit {
return testrate.percent
}
}
/**
* This represents a source of data.
*/
abstract class Oracle() { abstract fun retrieve(tenor: Tenor, date: LocalDate) : RatioUnit }
class ReutersOracle() : Oracle() {
override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit {
TODO("Reuters Oracle retrieval")
}
}
class TelerateOracle(page: String) : Oracle() {
override fun retrieve(tenor: Tenor, date: LocalDate): RatioUnit {
TODO("Telerate Oracle retrieval")
}
}
/**
* A Reference rate that is retrieved via an Oracle.
*/
open class OracleRetrievableReferenceRate(val oracle: Oracle, val tenor: Tenor, referenceRate: String) : ReferenceRate(referenceRate) {
override fun getAsOf(date: LocalDate?): RatioUnit {
return oracle.retrieve(tenor,date!!)
}
override fun toString(): String = "$name - $tenor"
}
// TODO: For further discussion.
operator fun Amount.times(other: RatioUnit): Amount = Amount((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
//operator fun Amount.times(other: FixedRate): Amount = Amount((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
//fun Amount.times(other: InterestRateSwap.RatioUnit): Amount = Amount((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()

@ -39,6 +39,8 @@ data class Amount(val pennies: Long, val currency: Currency) : Comparable<Amount
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
}
constructor(amount:BigDecimal, currency: Currency) : this(amount.toLong(), currency)
operator fun plus(other: Amount): Amount {
checkCurrency(other)
return Amount(Math.addExact(pennies, other.pennies), currency)
@ -58,7 +60,8 @@ data class Amount(val pennies: Long, val currency: Currency) : Comparable<Amount
operator fun div(other: Int): Amount = Amount(pennies / other, currency)
operator fun times(other: Int): Amount = Amount(Math.multiplyExact(pennies, other.toLong()), currency)
override fun toString(): String = currency.currencyCode + " " + (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString()
// override fun toString(): String = currency.currencyCode + " " + (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString()
override fun toString(): String = (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString()
override fun compareTo(other: Amount): Int {
checkCurrency(other)
@ -84,7 +87,9 @@ data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
/**
* Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
data class Tenor(var name:String)
data class Tenor(var name:String) {
override fun toString(): String = "$name"
}
/** Simple enum for returning accurals adjusted or unadjusted.
* We don't actually do anything with this yet though, so it's ignored for now.
@ -142,16 +147,25 @@ enum class DateRollConvention {
}
/** This forms the day part of the "Day Count Basis" used for interest calculation. */
/**
* This forms the day part of the "Day Count Basis" used for interest calculation.
* Note that the first character cannot be a number (enum naming constraints), so we drop that
* in the toString lest some people get confused. */
enum class DayCountBasisDay {
// We have to prefix 30 etc with a letter due to enum naming constraints.
D30, D30N, D30P, D30E, D30G, Actual, ActualJ, D30Z, D30F, Bus_SaoPaulo
D30, D30N, D30P, D30E, D30G, DActual, DActualJ, D30Z, D30F, DBus_SaoPaulo;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** This forms the year part of the "Day Count Basis" used for interest calculation. */
enum class DayCountBasisYear {
// Ditto above comment for years.
Y360, Y365F, Y365L, Y365Q, Y366, Actual, ActualA, Y365B, Y365, ISMA, ICMA, Y252
Y360, Y365F, Y365L, Y365Q, Y366, YActual, YActualA, Y365B, Y365, YISMA, YICMA, Y252;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** Whether the payment should be made before the due date, or after it. */
@ -284,16 +298,32 @@ open class BusinessCalendar private constructor(val holidayDates: List<LocalDate
}
return trialDate
}
/**
* Returns a date which is the inbound date plus/minus a given number of business days.
* TODO: Make more efficient if necessary
*/
fun moveBusinessDays(date: LocalDate, direction: DateRollDirection, i: Int): LocalDate {
require(i >= 0)
if ( i == 0 ) return date
var retDate = date
var ctr = 0
while (ctr < i) {
retDate = retDate.plusDays(direction.value)
if (isWorkingDay(retDate)) ctr++
}
return retDate
}
}
fun dayCountCalculator(startDate: LocalDate, endDate: LocalDate,
dcbYear: DayCountBasisYear,
dcbDay: DayCountBasisDay): BigDecimal {
dcbDay: DayCountBasisDay): Int {
// Right now we are only considering Actual/360 and 30/360 .. We'll do the rest later.
// TODO: The rest.
return when {
dcbDay == DayCountBasisDay.Actual && dcbYear == DayCountBasisYear.Y360 -> BigDecimal((endDate.toEpochDay() - startDate.toEpochDay()))
dcbDay == DayCountBasisDay.D30 && dcbYear == DayCountBasisYear.Y360 -> BigDecimal((endDate.year - startDate.year) * 360.0 + (endDate.monthValue - startDate.monthValue) * 30.0 + endDate.dayOfMonth - startDate.dayOfMonth)
dcbDay == DayCountBasisDay.DActual && dcbYear == DayCountBasisYear.Y360 -> (endDate.toEpochDay() - startDate.toEpochDay()).toInt()
dcbDay == DayCountBasisDay.D30 && dcbYear == DayCountBasisYear.Y360 -> ((endDate.year - startDate.year) * 360.0 + (endDate.monthValue - startDate.monthValue) * 30.0 + endDate.dayOfMonth - startDate.dayOfMonth).toInt()
else -> TODO("Can't calculate days using convention $dcbDay / $dcbYear")
}
}

@ -159,3 +159,5 @@ fun extractZipFile(zipPath: Path, toPath: Path) {
}
}
}
// TODO: Generic csv printing utility for clases.

@ -11,6 +11,7 @@ package core
import org.junit.Test
import java.time.LocalDate
import java.util.*
import kotlin.test.assertEquals
class FinanceTypesTest {
@ -90,7 +91,46 @@ class FinanceTypesTest {
assert(result == LocalDate.of(2016,12,28))
}
@Test
fun `calendar date advancing`() {
val ldn = BusinessCalendar.getInstance("London")
val firstDay = LocalDate.of(2015, 12, 20)
val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 21),
2 to LocalDate.of(2015, 12, 22),
3 to LocalDate.of(2015, 12, 23),
4 to LocalDate.of(2015, 12, 24),
5 to LocalDate.of(2015, 12, 29),
6 to LocalDate.of(2015, 12, 30),
7 to LocalDate.of(2015, 12, 31)
)
for ((inc, exp) in expected) {
var result = ldn.moveBusinessDays(firstDay, DateRollDirection.FORWARD, inc)
assertEquals(exp, result)
}
}
@Test
fun `calendar date preceeding`() {
val ldn = BusinessCalendar.getInstance("London")
val firstDay = LocalDate.of(2015, 12, 31)
val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 30),
2 to LocalDate.of(2015, 12, 29),
3 to LocalDate.of(2015, 12, 24),
4 to LocalDate.of(2015, 12, 23),
5 to LocalDate.of(2015, 12, 22),
6 to LocalDate.of(2015, 12, 21),
7 to LocalDate.of(2015, 12, 18)
)
for ((inc, exp) in expected) {
var result = ldn.moveBusinessDays(firstDay, DateRollDirection.BACKWARD, inc)
assertEquals(exp, result)
}
}

@ -0,0 +1,402 @@
/*
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
* set forth therein.
*
* All other rights reserved.
*/
package contracts
import core.*
import core.node.services.DummyTimestampingAuthority
import core.testutils.*
import org.junit.Test
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, 16),
effectiveDateAdjustment = null,
terminationDate = LocalDate.of(2026, 3, 16),
terminationDateAdjustment = null,
fixedRate = FixedRate(PercentageRatioUnit("1.677")),
dayCountBasisDay = DayCountBasisDay.D30,
dayCountBasisYear = DayCountBasisYear.Y360,
rollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
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 = 0,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriod = DateOffset.TWODAYS,
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 = InterestRateSwap.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),
valuationDate = "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 = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade1",
hashLegalDocs = "put hash here",
dailyInterestAmount = InterestRateSwap.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,
fixingPeriod = DateOffset.TWODAYS,
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 = InterestRateSwap.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),
valuationDate = "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 = OracleRetrievableReferenceRate(TelerateOracle("T3270"), Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
tradeID = "trade1",
hashLegalDocs = "put hash here",
dailyInterestAmount = InterestRateSwap.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 {
val attachments = MockStorageService().attachments
@Test
fun ok() {
val t = trade()
t.verify()
}
/**
* Generate an IRS txn - we'll need it for a few things.
*/
fun generateIRSTxn(irsSelect: Int): LedgerTransaction {
val dummyIRS = createDummyIRS(irsSelect)
val genTX: LedgerTransaction = run {
val gtx = InterestRateSwap().generateAgreement(
fixedLeg = dummyIRS.fixedLeg,
floatingLeg = dummyIRS.floatingLeg,
calculation = dummyIRS.calculation,
common = dummyIRS.common).apply {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
}
val stx = gtx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
}
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).outputs.filterIsInstance<InterestRateSwap.State>().single()
}
/**
* Test the generate
*/
@Test
fun generateIRS() {
// Tests aren't allowed to return things
generateIRSTxn(1)
}
@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 (it in fixings) {
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.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() {
var previousTXN = generateIRSTxn(1)
var currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
while (true) {
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
println("\n\n\n ***** Applying a fixing to $nextFixingDate \n\n\n")
var fixTX: LedgerTransaction = run {
val tx = TransactionBuilder()
val fixing = Pair(nextFixingDate, FixedRate("0.052".percent))
InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing)
with(tx) {
setTime(TEST_TX_TIME, DummyTimestampingAuthority.identity, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY)
timestamp(DUMMY_TIMESTAMPER)
}
val stx = tx.toSignedTransaction()
stx.verifyToLedgerTransaction(MockIdentityService, attachments)
}
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
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 `more rate tests`() {
val r1 = FixedRate(PercentageRatioUnit("10"))
val r2 = FixedRate(PercentageRatioUnit("10"))
// TODO: r1+r2 ? Do we want to allow these.
// TODO: r1*r2 ?
}
@Test
fun `reference rate testing`() {
val r1 = TestReferenceRate("5")
assert(100 * r1.getAsOf(null) == 5)
}
@Test
fun `expression calculation testing`() {
val dummyIRS = singleIRS()
val v = FixedRate(PercentageRatioUnit("4.5"))
val stuffToPrint: ArrayList<String> = arrayListOf(
"fixedLeg.notional.pennies",
"fixedLeg.fixedRate.ratioUnit",
"fixedLeg.fixedRate.ratioUnit.value",
"floatingLeg.notional.pennies",
"fixedLeg.fixedRate",
"currentBusinessDate",
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
"fixedLeg.notional.currency.currencyCode",
"fixedLeg.notional.pennies * 10",
"fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value",
"(fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360 ",
"(fixedLeg.notional.pennies * (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)
var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), InterestRateSwap.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.
}
fun trade(): TransactionGroupDSL<InterestRateSwap.State> {
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
transaction("Agreement") {
output("irs post agreement") { singleIRS() }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
}
transaction("Fix") {
input("irs post agreement")
output("irs post first fixing") { "irs post agreement".output }
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
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() }
timestamp(TEST_TX_TIME)
}
}
return txgroup
}
}

@ -37,6 +37,7 @@ inline fun <R> rootCauseExceptions(body: () -> R) : R {
object TestUtils {
val keypair = generateKeyPair()
val keypair2 = generateKeyPair()
val keypair3 = generateKeyPair()
}
// A dummy time at which we will be pretending test transactions are created.
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
@ -44,14 +45,22 @@ val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
// A few dummy values for testing.
val MEGA_CORP_KEY = TestUtils.keypair
val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
val ALICE_KEY = generateKeyPair()
val ALICE = ALICE_KEY.public
val BOB_KEY = generateKeyPair()
val BOB = BOB_KEY.public
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
@ -70,7 +79,8 @@ val TEST_PROGRAM_MAP: Map<SecureHash, Class<out Contract>> = mapOf(
CP_PROGRAM_ID to CommercialPaper::class.java,
JavaCommercialPaper.JCP_PROGRAM_ID to JavaCommercialPaper::class.java,
CROWDFUND_PROGRAM_ID to CrowdFund::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java
DUMMY_PROGRAM_ID to DummyContract::class.java,
IRS_PROGRAM_ID to InterestRateSwap::class.java
)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////