diff --git a/src/main/kotlin/core/node/AbstractNode.kt b/src/main/kotlin/core/node/AbstractNode.kt index 4e4b070150..1fb0c223c2 100644 --- a/src/main/kotlin/core/node/AbstractNode.kt +++ b/src/main/kotlin/core/node/AbstractNode.kt @@ -115,7 +115,17 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, keyManagement = E2ETestKeyManagementService() makeInterestRatesOracleService() api = APIServerImpl(this) + makeTimestampingService(timestamperAddress) + identity = makeIdentityService() + // This object doesn't need to be referenced from this class because it registers handlers on the network + // service and so that keeps it from being collected. + DataVendingService(net, storage) + + return this + } + + private fun makeTimestampingService(timestamperAddress: NodeInfo?) { // Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are // given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping. val tsid = if (timestamperAddress != null) { @@ -126,14 +136,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, NodeInfo(net.myAddress, storage.myLegalIdentity) } (services.networkMapCache as MockNetworkMapCache).timestampingNodes.add(tsid) - - identity = makeIdentityService() - - // This object doesn't need to be referenced from this class because it registers handlers on the network - // service and so that keeps it from being collected. - DataVendingService(net, storage) - - return this } lateinit var interestRatesService: NodeInterestRates.Service diff --git a/src/main/kotlin/core/node/services/NetworkMapCache.kt b/src/main/kotlin/core/node/services/NetworkMapCache.kt index 5275c80cdd..574c1f3d2f 100644 --- a/src/main/kotlin/core/node/services/NetworkMapCache.kt +++ b/src/main/kotlin/core/node/services/NetworkMapCache.kt @@ -32,6 +32,7 @@ interface NetworkMapCache { val timestampingNodes: List val ratesOracleNodes: List val partyNodes: List + val regulators: List fun nodeForPartyName(name: String): NodeInfo? = partyNodes.singleOrNull { it.identity.name == name } } @@ -43,6 +44,7 @@ class MockNetworkMapCache : NetworkMapCache { override val timestampingNodes = Collections.synchronizedList(ArrayList()) override val ratesOracleNodes = Collections.synchronizedList(ArrayList()) override val partyNodes = Collections.synchronizedList(ArrayList()) + override val regulators = Collections.synchronizedList(ArrayList()) init { partyNodes.add(NodeInfo(MockAddress("bankC:8080"), Party("Bank C", DummyPublicKey("Bank C")))) diff --git a/src/main/kotlin/core/testing/IRSSimulation.kt b/src/main/kotlin/core/testing/IRSSimulation.kt new file mode 100644 index 0000000000..472ba47f19 --- /dev/null +++ b/src/main/kotlin/core/testing/IRSSimulation.kt @@ -0,0 +1,141 @@ +/* + * 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 core.testing + +import com.fasterxml.jackson.module.kotlin.readValue +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.SettableFuture +import contracts.InterestRateSwap +import core.* +import core.crypto.SecureHash +import core.node.services.FixedIdentityService +import core.node.services.linearHeadsOfType +import core.utilities.JsonSupport +import protocols.TwoPartyDealProtocol +import java.time.LocalDate +import java.util.* + + +/** + * A simulation in which banks execute interest rate swaps with each other, including the fixing events. + */ +class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) { + val om = JsonSupport.createDefaultMapper(FixedIdentityService(network.identities)) + + init { + currentDay = LocalDate.of(2016, 3, 10) // Should be 12th but the actual first fixing date gets rolled backwards. + } + + private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) + + override fun start() { + startIRSDealBetween(0, 1).success { + // Next iteration is a pause. + executeOnNextIteration.add {} + executeOnNextIteration.add { + // Keep fixing until there's no more left to do. + doNextFixing(0, 1)?.addListener(object : Runnable { + override fun run() { + // Pause for an iteration. + executeOnNextIteration.add {} + executeOnNextIteration.add { + doNextFixing(0, 1)?.addListener(this, RunOnCallerThread) + } + } + }, RunOnCallerThread) + } + } + } + + private fun doNextFixing(i: Int, j: Int): ListenableFuture<*>? { + println("Doing a fixing between $i and $j") + val node1: SimulatedNode = banks[i] + val node2: SimulatedNode = banks[j] + + val sessionID = random63BitValue() + val swaps: Map> = node1.services.walletService.linearHeadsOfType() + val theDealRef: StateAndRef = swaps.values.single() + + // Do we have any more days left in this deal's lifetime? If not, return. + val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null + extraNodeLabels[node1] = "Fixing event on $nextFixingDate" + extraNodeLabels[node2] = "Fixing event on $nextFixingDate" + + // For some reason the first fix is always before the effective date. + if (nextFixingDate > currentDay) + currentDay = nextFixingDate + + val sideA = TwoPartyDealProtocol.Floater(node2.net.myAddress, sessionID, timestamper.info, + theDealRef, node1.services.keyManagementService.freshKey(), sessionID) + val sideB = TwoPartyDealProtocol.Fixer(node1.net.myAddress, timestamper.info.identity, + theDealRef, sessionID) + + linkConsensus(listOf(node1, node2, regulators[0]), sideB) + linkProtocolProgress(node1, sideA) + linkProtocolProgress(node2, sideB) + + // We have to start the protocols in separate iterations, as adding to the SMM effectively 'iterates' that node + // in the simulation, so if we don't do this then the two sides seem to act simultaneously. + + val retFuture = SettableFuture.create() + val futA = node1.smm.add("floater", sideA) + executeOnNextIteration += { + val futB = node2.smm.add("fixer", sideB) + Futures.allAsList(futA, futB).then { + retFuture.set(null) + } + } + return retFuture + } + + private fun startIRSDealBetween(i: Int, j: Int): ListenableFuture { + val node1: SimulatedNode = banks[i] + val node2: SimulatedNode = banks[j] + + extraNodeLabels[node1] = "Setting up deal" + extraNodeLabels[node2] = "Setting up deal" + + // We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't + // have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable. + // TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface. + val irs = om.readValue(javaClass.getResource("trade.json")) + irs.fixedLeg.fixedRatePayer = node1.info.identity + irs.floatingLeg.floatingRatePayer = node2.info.identity + + if (irs.fixedLeg.effectiveDate < irs.floatingLeg.effectiveDate) + currentDay = irs.fixedLeg.effectiveDate + else + currentDay = irs.floatingLeg.effectiveDate + + val sessionID = random63BitValue() + + val instigator = TwoPartyDealProtocol.Instigator(node2.net.myAddress, timestamper.info, + irs, node1.services.keyManagementService.freshKey(), sessionID) + val acceptor = TwoPartyDealProtocol.Acceptor(node1.net.myAddress, timestamper.info.identity, + irs, sessionID) + + // TODO: Eliminate the need for linkProtocolProgress + linkConsensus(listOf(node1, node2, regulators[0]), acceptor) + linkProtocolProgress(node1, instigator) + linkProtocolProgress(node2, acceptor) + + val instigatorFuture: ListenableFuture = node1.smm.add("instigator", instigator) + + return Futures.transformAsync(Futures.allAsList(instigatorFuture, node2.smm.add("acceptor", acceptor))) { + instigatorFuture + } + } + + override fun iterate() { + if (executeOnNextIteration.isNotEmpty()) + executeOnNextIteration.removeAt(0)() + super.iterate() + } +} \ No newline at end of file diff --git a/src/main/kotlin/core/testing/MockNode.kt b/src/main/kotlin/core/testing/MockNode.kt index f2c6695591..4fd00ae3d6 100644 --- a/src/main/kotlin/core/testing/MockNode.kt +++ b/src/main/kotlin/core/testing/MockNode.kt @@ -93,6 +93,8 @@ class MockNetwork(private val threadPerNode: Boolean = false, mockNet.identities.add(storage.myLegalIdentity) return this } + + val place: PhysicalLocation get() = info.physicalLocation!! } /** Returns a started node, optionally created by the passed factory method */ diff --git a/src/main/kotlin/core/testing/Simulation.kt b/src/main/kotlin/core/testing/Simulation.kt new file mode 100644 index 0000000000..ae91abb2fd --- /dev/null +++ b/src/main/kotlin/core/testing/Simulation.kt @@ -0,0 +1,213 @@ +/* + * 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 core.testing + +import com.google.common.util.concurrent.ListenableFuture +import core.node.NodeConfiguration +import core.node.services.CityDatabase +import core.node.services.MockNetworkMapCache +import core.node.services.NodeInfo +import core.node.services.PhysicalLocation +import core.protocols.ProtocolLogic +import core.then +import core.utilities.ProgressTracker +import rx.Observable +import rx.subjects.PublishSubject +import java.nio.file.Path +import java.time.LocalDate +import java.util.* + +/** + * Base class for network simulations that are based on the unit test / mock environment. + * + * Sets up some nodes that can run protocols between each other, and exposes their progress trackers. Provides banks + * in a few cities around the world. + */ +abstract class Simulation(val runAsync: Boolean, + val latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) { + init { + if (!runAsync && latencyInjector != null) + throw IllegalArgumentException("The latency injector is only useful when using manual pumping.") + } + + val bankLocations = listOf("London", "Frankfurt", "Rome") + + // This puts together a mock network of SimulatedNodes. + + open class SimulatedNode(dir: Path, config: NodeConfiguration, mockNet: MockNetwork, + withTimestamper: NodeInfo?) : MockNetwork.MockNode(dir, config, mockNet, withTimestamper) { + override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity] + } + + inner class BankFactory : MockNetwork.Factory { + var counter = 0 + + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode { + val letter = 'A' + counter + val city = bankLocations[counter++ % bankLocations.size] + val cfg = object : NodeConfiguration { + // TODO: Set this back to "Bank of $city" after video day. + override val myLegalName: String = "Bank $letter" + override val exportJMXto: String = "" + override val nearestCity: String = city + } + val node = SimulatedNode(dir, cfg, network, timestamperAddr) + // TODO: This is obviously bogus: there should be a single network map for the whole simulated network. + (node.services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += ratesOracle.info + (node.services.networkMapCache as MockNetworkMapCache).regulators += regulators.map { it.info } + return node + } + + fun createAll(): List = bankLocations.map { network.createNode(timestamper.info, nodeFactory = this) as SimulatedNode } + } + + val bankFactory = BankFactory() + + object TimestampingNodeFactory : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode { + val cfg = object : NodeConfiguration { + override val myLegalName: String = "Timestamping Service" // A magic string recognised by the CP contract + override val exportJMXto: String = "" + override val nearestCity: String = "Zurich" + } + return SimulatedNode(dir, cfg, network, timestamperAddr) + } + } + + object RatesOracleFactory : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode { + val cfg = object : NodeConfiguration { + override val myLegalName: String = "Rates Service Provider" + override val exportJMXto: String = "" + override val nearestCity: String = "Madrid" + } + + val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) { + override fun makeInterestRatesOracleService() { + super.makeInterestRatesOracleService() + interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt")) + (services.networkMapCache as MockNetworkMapCache).ratesOracleNodes += info + } + } + return n + } + } + + object RegulatorFactory : MockNetwork.Factory { + override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, timestamperAddr: NodeInfo?): MockNetwork.MockNode { + val cfg = object : NodeConfiguration { + override val myLegalName: String = "Regulator A" + override val exportJMXto: String = "" + override val nearestCity: String = "Paris" + } + + val n = object : SimulatedNode(dir, cfg, network, timestamperAddr) { + // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. + // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. + // But that's fine for visualisation purposes. + } + return n + } + } + + val network = MockNetwork(false) + + val timestamper: SimulatedNode = network.createNode(null, nodeFactory = TimestampingNodeFactory) as SimulatedNode + val ratesOracle: SimulatedNode = network.createNode(null, nodeFactory = RatesOracleFactory) as SimulatedNode + val serviceProviders: List = listOf(timestamper, ratesOracle) + val banks: List = bankFactory.createAll() + val regulators: List = listOf(network.createNode(null, nodeFactory = RegulatorFactory) as SimulatedNode) + + private val _allProtocolSteps = PublishSubject.create>() + private val _doneSteps = PublishSubject.create>() + val allProtocolSteps: Observable> = _allProtocolSteps + val doneSteps: Observable> = _doneSteps + + private var pumpCursor = 0 + + /** + * The current simulated date. By default this never changes. If you want it to change, you should do so from + * within your overridden [iterate] call. Changes in the current day surface in the [dateChanges] observable. + */ + var currentDay: LocalDate = LocalDate.now() + protected set(value) { + field = value + _dateChanges.onNext(value) + } + + private val _dateChanges = PublishSubject.create() + val dateChanges: Observable = _dateChanges + + /** + * A place for simulations to stash human meaningful text about what the node is "thinking", which might appear + * in the UI somewhere. + */ + val extraNodeLabels = Collections.synchronizedMap(HashMap()) + + /** + * Iterates the simulation by one step. + * + * The default implementation circles around the nodes, pumping until one of them handles a message. The next call + * will carry on from where this one stopped. In an environment where you want to take actions between anything + * interesting happening, or control the precise speed at which things operate (beyond the latency injector), this + * is a useful way to do things. + */ + open fun iterate() { + // Keep going until one of the nodes has something to do, or we have checked every node. + val endpoints = network.messagingNetwork.endpoints + var countDown = endpoints.size + while (countDown > 0) { + val handledMessage = endpoints[pumpCursor].pump(false) + if (handledMessage) break + // If this node had nothing to do, advance the cursor with wraparound and try again. + pumpCursor = (pumpCursor + 1) % endpoints.size + countDown-- + } + } + + protected fun linkProtocolProgress(node: SimulatedNode, protocol: ProtocolLogic<*>) { + val pt = protocol.progressTracker ?: return + pt.changes.subscribe { change: ProgressTracker.Change -> + // Runs on node thread. + _allProtocolSteps.onNext(Pair(node, change)) + } + // This isn't technically a "change" but it helps with UIs to send this notification of the first step. + _allProtocolSteps.onNext(Pair(node, ProgressTracker.Change.Position(pt, pt.steps[1]))) + } + + protected fun linkConsensus(nodes: Collection, protocol: ProtocolLogic<*>) { + protocol.progressTracker?.changes?.subscribe { change: ProgressTracker.Change -> + // Runs on node thread. + if (protocol.progressTracker!!.currentStep == ProgressTracker.DONE) { + _doneSteps.onNext(nodes) + } + } + } + + open fun start() {} + + fun stop() { + network.nodes.forEach { it.stop() } + } + + /** + * Given a function that returns a future, iterates that function with arguments like (0, 1), (1, 2), (2, 3) etc + * each time the returned future completes. + */ + fun startTradingCircle(tradeBetween: (indexA: Int, indexB: Int) -> ListenableFuture<*>) { + fun next(i: Int, j: Int) { + tradeBetween(i, j).then { + val ni = (i + 1) % banks.size + val nj = (j + 1) % banks.size + next(ni, nj) + } + } + next(0, 1) + } +} \ No newline at end of file diff --git a/src/main/kotlin/core/testing/TradeSimulation.kt b/src/main/kotlin/core/testing/TradeSimulation.kt new file mode 100644 index 0000000000..f010eae52b --- /dev/null +++ b/src/main/kotlin/core/testing/TradeSimulation.kt @@ -0,0 +1,60 @@ +/* + * 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 core.testing + +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture +import contracts.CommercialPaper +import core.* +import core.node.services.NodeWalletService +import core.utilities.BriefLogFormatter +import protocols.TwoPartyTradeProtocol +import java.time.Instant + +/** + * Simulates a never ending series of trades that go pair-wise through the banks (e.g. A and B trade with each other, + * then B and C trade with each other, then C and A etc). + */ +class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(runAsync, latencyInjector) { + override fun start() { + BriefLogFormatter.loggingOn("bank", "core.TransactionGroup", "recordingmap") + startTradingCircle { i, j -> tradeBetween(i, j) } + } + + private fun tradeBetween(buyerBankIndex: Int, sellerBankIndex: Int): ListenableFuture> { + val buyer = banks[buyerBankIndex] + val seller = banks[sellerBankIndex] + + (buyer.services.walletService as NodeWalletService).fillWithSomeTestCash(1500.DOLLARS) + + val issuance = run { + val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days) + tx.setTime(Instant.now(), timestamper.info.identity, 30.seconds) + tx.signWith(timestamper.storage.myLegalIdentityKey) + tx.signWith(seller.storage.myLegalIdentityKey) + tx.toSignedTransaction(true) + } + seller.services.storageService.validatedTransactions[issuance.id] = issuance + + val sessionID = random63BitValue() + val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, timestamper.info.identity, + 1000.DOLLARS, CommercialPaper.State::class.java, sessionID) + val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, timestamper.info, + issuance.tx.outRef(0), 1000.DOLLARS, seller.storage.myLegalIdentityKey, sessionID) + + linkConsensus(listOf(buyer, seller, timestamper), sellerProtocol) + linkProtocolProgress(buyer, buyerProtocol) + linkProtocolProgress(seller, sellerProtocol) + + val buyerFuture = buyer.smm.add("bank.$buyerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyerProtocol) + val sellerFuture = seller.smm.add("bank.$sellerBankIndex.${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", sellerProtocol) + + return Futures.successfulAsList(buyerFuture, sellerFuture) + } +} \ No newline at end of file diff --git a/src/main/kotlin/protocols/TwoPartyDealProtocol.kt b/src/main/kotlin/protocols/TwoPartyDealProtocol.kt index 56a55d5e64..29de9746bf 100644 --- a/src/main/kotlin/protocols/TwoPartyDealProtocol.kt +++ b/src/main/kotlin/protocols/TwoPartyDealProtocol.kt @@ -73,8 +73,11 @@ object TwoPartyDealProtocol { object TIMESTAMPING : ProgressTracker.Step("Timestamping transaction") object SENDING_SIGS : ProgressTracker.Step("Sending transaction signatures to other party") object RECORDING : ProgressTracker.Step("Recording completed transaction") + object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator") - fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING) + fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, TIMESTAMPING, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR).apply { + childrenFor[TIMESTAMPING] = TimestampingProtocol.tracker() + } } @Suspendable @@ -149,6 +152,15 @@ object TwoPartyDealProtocol { logger.trace { "Deal stored" } + val regulators = serviceHub.networkMapCache.regulators + if (regulators.isNotEmpty()) { + // Copy the transaction to every regulator in the network. This is obviously completely bogus, it's + // just for demo purposes. + for (regulator in regulators) { + send("regulator.all.seeing.eye", regulator.address, 0, fullySigned) + } + } + return fullySigned } diff --git a/src/main/resources/core/testing/example.rates.txt b/src/main/resources/core/testing/example.rates.txt new file mode 100644 index 0000000000..85063fd245 --- /dev/null +++ b/src/main/resources/core/testing/example.rates.txt @@ -0,0 +1,51 @@ +# Some pretend noddy rate fixes, for the interest rate oracles. + +3M USD 2016-03-16 1M = 0.678 +3M USD 2016-03-16 2M = 0.655 +EURIBOR 2016-03-15 1M = 0.123 +EURIBOR 2016-03-15 2M = 0.111 + +3M USD 2016-03-08 3M = 0.0063515 +3M USD 2016-06-08 3M = 0.0063520 +3M USD 2016-09-08 3M = 0.0063521 +3M USD 2016-12-08 3M = 0.0063515 +3M USD 2017-03-08 3M = 0.0063525 +3M USD 2017-06-08 3M = 0.0063530 +3M USD 2017-09-07 3M = 0.0063531 +3M USD 2017-12-07 3M = 0.0063532 +3M USD 2018-03-08 3M = 0.0063533 +3M USD 2018-06-07 3M = 0.0063534 +3M USD 2018-09-06 3M = 0.0063535 +3M USD 2018-12-06 3M = 0.0063536 +3M USD 2019-03-07 3M = 0.0063537 +3M USD 2019-06-06 3M = 0.0063538 +3M USD 2019-09-06 3M = 0.0063539 +3M USD 2019-12-06 3M = 0.0063540 +3M USD 2020-03-06 3M = 0.0063541 +3M USD 2020-06-08 3M = 0.0063542 +3M USD 2020-09-08 3M = 0.0063543 +3M USD 2020-12-08 3M = 0.0063544 +3M USD 2021-03-08 3M = 0.0063545 +3M USD 2021-06-08 3M = 0.0063546 +3M USD 2021-09-08 3M = 0.0063547 +3M USD 2021-12-08 3M = 0.0063548 +3M USD 2022-03-08 3M = 0.0063549 +3M USD 2022-06-08 3M = 0.0063550 +3M USD 2022-09-08 3M = 0.0063551 +3M USD 2022-12-08 3M = 0.0063553 +3M USD 2023-03-08 3M = 0.0063554 +3M USD 2023-06-08 3M = 0.0063555 +3M USD 2023-09-07 3M = 0.0063556 +3M USD 2023-12-07 3M = 0.0063557 +3M USD 2024-03-07 3M = 0.0063558 +3M USD 2024-06-06 3M = 0.0063559 +3M USD 2024-09-06 3M = 0.0063560 +3M USD 2024-12-06 3M = 0.0063561 +3M USD 2025-03-06 3M = 0.0063562 +3M USD 2025-06-06 3M = 0.0063563 +3M USD 2025-09-08 3M = 0.0063564 +3M USD 2025-12-08 3M = 0.0063565 +3M USD 2026-03-06 3M = 0.0063566 +3M USD 2026-06-08 3M = 0.0063567 +3M USD 2026-09-08 3M = 0.0063568 +3M USD 2026-12-08 3M = 0.0063569 diff --git a/src/main/resources/core/testing/trade.json b/src/main/resources/core/testing/trade.json new file mode 100644 index 0000000000..ef3737722c --- /dev/null +++ b/src/main/resources/core/testing/trade.json @@ -0,0 +1,104 @@ +{ + "fixedLeg": { + "fixedRatePayer": "Bank A", + "notional": { + "pennies": 2500000000, + "currency": "USD" + }, + "paymentFrequency": "SemiAnnual", + "effectiveDate": "2016-03-16", + "effectiveDateAdjustment": null, + "terminationDate": "2026-03-16", + "terminationDateAdjustment": null, + "fixedRate": { + "ratioUnit": { + "value": "0.01676" + } + }, + "dayCountBasisDay": "D30", + "dayCountBasisYear": "Y360", + "rollConvention": "ModifiedFollowing", + "dayInMonth": 10, + "paymentRule": "InArrears", + "paymentDelay": 0, + "paymentCalendar": "London", + "interestPeriodAdjustment": "Adjusted" + }, + "floatingLeg": { + "floatingRatePayer": "Bank B", + "notional": { + "pennies": 2500000000, + "currency": "USD" + }, + "paymentFrequency": "Quarterly", + "effectiveDate": "2016-03-12", + "effectiveDateAdjustment": null, + "terminationDate": "2026-03-12", + "terminationDateAdjustment": null, + "dayCountBasisDay": "D30", + "dayCountBasisYear": "Y360", + "rollConvention": "ModifiedFollowing", + "fixingRollConvention": "ModifiedFollowing", + "dayInMonth": 10, + "resetDayInMonth": 10, + "paymentRule": "InArrears", + "paymentDelay": 0, + "paymentCalendar": [ "London" ], + "interestPeriodAdjustment": "Adjusted", + "fixingPeriod": "TWODAYS", + "resetRule": "InAdvance", + "fixingsPerPayment": "Quarterly", + "fixingCalendar": [ "NewYork" ], + "index": "3M USD", + "indexSource": "Rates Service Provider", + "indexTenor": { + "name": "3M" + } + }, + "calculation": { + "expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))", + "floatingLegPaymentSchedule": { + }, + "fixedLegPaymentSchedule": { + } + }, + "common": { + "baseCurrency": "EUR", + "eligibleCurrency": "EUR", + "eligibleCreditSupport": "Cash in an Eligible Currency", + "independentAmounts": { + "pennies": 0, + "currency": "EUR" + }, + "threshold": { + "pennies": 0, + "currency": "EUR" + }, + "minimumTransferAmount": { + "pennies": 25000000, + "currency": "EUR" + }, + "rounding": { + "pennies": 1000000, + "currency": "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": { + "oracle": "Rates Service Provider", + "tenor": { + "name": "6M" + }, + "ratioUnit": null, + "name": "EONIA" + }, + "addressForTransfers": "", + "exposure": {}, + "localBusinessDay": [ "London" , "NewYork" ], + "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360", + "tradeID": "tradeXXX", + "hashLegalDocs": "put hash here" + }, + "programRef": "1E6BBA305D445341F0026E51B6C7F3ACB834AFC6C2510C0EF7BC0477235EFECF" +} \ No newline at end of file