mirror of
https://github.com/corda/corda.git
synced 2025-06-18 15:18:16 +00:00
Merge pull request #72 from corda/clint-simmdemointegrationtest
Add SIMM valuation demo integration test
This commit is contained in:
@ -25,49 +25,53 @@ import java.time.LocalDateTime
|
|||||||
* the java.time API, some core types, and Kotlin data classes.
|
* the java.time API, some core types, and Kotlin data classes.
|
||||||
*/
|
*/
|
||||||
object JsonSupport {
|
object JsonSupport {
|
||||||
|
val javaTimeModule : Module by lazy {
|
||||||
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
|
SimpleModule("java.time").apply {
|
||||||
val mapper = ServiceHubObjectMapper(identities)
|
addSerializer(LocalDate::class.java, ToStringSerializer)
|
||||||
mapper.enable(SerializationFeature.INDENT_OUTPUT)
|
addDeserializer(LocalDate::class.java, LocalDateDeserializer)
|
||||||
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
|
||||||
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
|
addSerializer(LocalDateTime::class.java, ToStringSerializer)
|
||||||
|
}
|
||||||
val timeModule = SimpleModule("java.time")
|
|
||||||
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
|
|
||||||
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
|
|
||||||
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
|
|
||||||
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
|
|
||||||
|
|
||||||
val cordaModule = SimpleModule("core")
|
|
||||||
cordaModule.addSerializer(Party::class.java, PartySerializer)
|
|
||||||
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
|
|
||||||
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
|
|
||||||
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
|
|
||||||
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
|
|
||||||
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
|
|
||||||
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
|
|
||||||
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
|
||||||
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
|
|
||||||
|
|
||||||
// For ed25519 pubkeys
|
|
||||||
cordaModule.addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
|
|
||||||
cordaModule.addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
|
|
||||||
|
|
||||||
// For composite keys
|
|
||||||
cordaModule.addSerializer(CompositeKey::class.java, CompositeKeySerializer)
|
|
||||||
cordaModule.addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
|
|
||||||
|
|
||||||
// For NodeInfo
|
|
||||||
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
|
|
||||||
cordaModule.addSerializer(NodeInfo::class.java, NodeInfoSerializer)
|
|
||||||
cordaModule.addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
|
|
||||||
|
|
||||||
mapper.registerModule(timeModule)
|
|
||||||
mapper.registerModule(cordaModule)
|
|
||||||
mapper.registerModule(KotlinModule())
|
|
||||||
return mapper
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cordaModule : Module by lazy {
|
||||||
|
SimpleModule("core").apply {
|
||||||
|
addSerializer(Party::class.java, PartySerializer)
|
||||||
|
addDeserializer(Party::class.java, PartyDeserializer)
|
||||||
|
addSerializer(BigDecimal::class.java, ToStringSerializer)
|
||||||
|
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
|
||||||
|
addSerializer(SecureHash::class.java, SecureHashSerializer)
|
||||||
|
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
|
||||||
|
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
|
||||||
|
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
||||||
|
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
|
||||||
|
|
||||||
|
// For ed25519 pubkeys
|
||||||
|
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
|
||||||
|
addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
|
||||||
|
|
||||||
|
// For composite keys
|
||||||
|
addSerializer(CompositeKey::class.java, CompositeKeySerializer)
|
||||||
|
addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
|
||||||
|
|
||||||
|
// For NodeInfo
|
||||||
|
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
|
||||||
|
addSerializer(NodeInfo::class.java, NodeInfoSerializer)
|
||||||
|
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDefaultMapper(identities: IdentityService): ObjectMapper =
|
||||||
|
ServiceHubObjectMapper(identities).apply {
|
||||||
|
enable(SerializationFeature.INDENT_OUTPUT)
|
||||||
|
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
||||||
|
enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
|
||||||
|
|
||||||
|
registerModule(javaTimeModule)
|
||||||
|
registerModule(cordaModule)
|
||||||
|
registerModule(KotlinModule())
|
||||||
|
}
|
||||||
|
|
||||||
class ServiceHubObjectMapper(val identities: IdentityService) : ObjectMapper()
|
class ServiceHubObjectMapper(val identities: IdentityService) : ObjectMapper()
|
||||||
|
|
||||||
object ToStringSerializer : JsonSerializer<Any>() {
|
object ToStringSerializer : JsonSerializer<Any>() {
|
||||||
|
@ -34,6 +34,18 @@ sourceSets {
|
|||||||
srcDir "../../config/test"
|
srcDir "../../config/test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
integrationTest {
|
||||||
|
kotlin {
|
||||||
|
compileClasspath += main.output + test.output
|
||||||
|
runtimeClasspath += main.output + test.output
|
||||||
|
srcDir file('src/integration-test/kotlin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
integrationTestCompile.extendsFrom testCompile
|
||||||
|
integrationTestRuntime.extendsFrom testRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -105,6 +117,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task integrationTest(type: Test, dependsOn: []) {
|
||||||
|
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||||
|
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
task npmInstall(type: Exec) {
|
task npmInstall(type: Exec) {
|
||||||
workingDir 'src/main/web'
|
workingDir 'src/main/web'
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package net.corda.vega
|
||||||
|
|
||||||
|
import com.opengamma.strata.product.common.BuySell
|
||||||
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.node.services.ServiceInfo
|
||||||
|
import net.corda.node.driver.NodeHandle
|
||||||
|
import net.corda.node.driver.driver
|
||||||
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
|
import net.corda.testing.IntegrationTestCategory
|
||||||
|
import net.corda.testing.getHostAndPort
|
||||||
|
import net.corda.testing.http.HttpApi
|
||||||
|
import net.corda.vega.api.PortfolioApi
|
||||||
|
import net.corda.vega.api.PortfolioApiUtils
|
||||||
|
import net.corda.vega.api.SwapDataModel
|
||||||
|
import net.corda.vega.api.SwapDataView
|
||||||
|
import net.corda.vega.portfolio.Portfolio
|
||||||
|
import org.junit.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
|
class SimmValuationTest: IntegrationTestCategory {
|
||||||
|
private companion object {
|
||||||
|
// SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source.
|
||||||
|
val valuationDate = LocalDate.parse("2016-06-06")
|
||||||
|
val nodeALegalName = "Bank A"
|
||||||
|
val nodeBLegalName = "Bank B"
|
||||||
|
val testTradeId = "trade1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `runs SIMM valuation demo`() {
|
||||||
|
driver(isDebug = true) {
|
||||||
|
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow()
|
||||||
|
val nodeA = getSimmNodeApi(startNode(nodeALegalName))
|
||||||
|
val nodeB = getSimmNodeApi(startNode(nodeBLegalName))
|
||||||
|
val nodeBParty = getPartyWithName(nodeA, nodeBLegalName)
|
||||||
|
val nodeAParty = getPartyWithName(nodeB, nodeALegalName)
|
||||||
|
|
||||||
|
assert(createTradeBetween(nodeA, nodeBParty, testTradeId))
|
||||||
|
assert(tradeExists(nodeB, nodeAParty, testTradeId))
|
||||||
|
assert(runValuationsBetween(nodeA, nodeBParty))
|
||||||
|
assert(valuationExists(nodeB, nodeAParty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSimmNodeApi(futureNode: Future<NodeHandle>): HttpApi {
|
||||||
|
val nodeAddr = futureNode.getOrThrow().config.getHostAndPort("webAddress")
|
||||||
|
return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPartyWithName(partyApi: HttpApi, countryparty: String): PortfolioApi.ApiParty =
|
||||||
|
getAvailablePartiesFor(partyApi).counterparties.single { it.text == countryparty }
|
||||||
|
|
||||||
|
private fun getAvailablePartiesFor(partyApi: HttpApi): PortfolioApi.AvailableParties {
|
||||||
|
return partyApi.getJson<PortfolioApi.AvailableParties>("whoami")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTradeBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty, tradeId: String): Boolean {
|
||||||
|
val trade = SwapDataModel(tradeId, "desc", valuationDate, "EUR_FIXED_1Y_EURIBOR_3M",
|
||||||
|
valuationDate, LocalDate.parse("2020-01-02"), BuySell.BUY, BigDecimal.valueOf(1000), BigDecimal.valueOf(0.1))
|
||||||
|
return partyApi.putJson("${counterparty.id}/trades", trade)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tradeExists(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty, tradeId: String): Boolean {
|
||||||
|
val trades = partyApi.getJson<Array<SwapDataView>>("${counterparty.id}/trades")
|
||||||
|
return (trades.find { it.id == tradeId } != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runValuationsBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty): Boolean {
|
||||||
|
return partyApi.postJson("${counterparty.id}/portfolio/valuations/calculate", PortfolioApi.ValuationCreationParams(valuationDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun valuationExists(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty): Boolean {
|
||||||
|
val valuations = partyApi.getJson<PortfolioApiUtils.ValuationsView>("${counterparty.id}/portfolio/valuations")
|
||||||
|
return (valuations.initialMargin.call["total"] != 0.0)
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
package net.corda.vega.api
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A small JSON DSL to create structures for APIs on the fly that mimic JSON in structure.
|
|
||||||
* Use: json { obj("a" to 100, "b" to "hello", "c" to arr(1, 2, "c")) }
|
|
||||||
*/
|
|
||||||
class JsonBuilder {
|
|
||||||
fun obj(vararg objs: Pair<String, Any>): Map<String, Any> {
|
|
||||||
return objs.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun arr(vararg objs: Any): List<Any> {
|
|
||||||
return objs.toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun json(body: JsonBuilder.() -> Map<String, Any>): Map<String, Any> {
|
|
||||||
val jsonWrapper = JsonBuilder()
|
|
||||||
return jsonWrapper.body()
|
|
||||||
}
|
|
@ -98,11 +98,9 @@ class PortfolioApi(val rpc: CordaRPCOps) {
|
|||||||
@Path("business-date")
|
@Path("business-date")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
fun getBusinessDate(): Any {
|
fun getBusinessDate(): Any {
|
||||||
return json {
|
return mapOf(
|
||||||
obj(
|
"business-date" to LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate()
|
||||||
"business-date" to LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate()
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,12 +193,10 @@ class PortfolioApi(val rpc: CordaRPCOps) {
|
|||||||
return withParty(partyName) { party ->
|
return withParty(partyName) { party ->
|
||||||
val trades = getTradesWith(party)
|
val trades = getTradesWith(party)
|
||||||
val portfolio = Portfolio(trades)
|
val portfolio = Portfolio(trades)
|
||||||
val summary = json {
|
val summary = mapOf(
|
||||||
obj(
|
"trades" to portfolio.trades.size,
|
||||||
"trades" to portfolio.trades.size,
|
"notional" to portfolio.getNotionalForParty(ownParty).toDouble()
|
||||||
"notional" to portfolio.getNotionalForParty(ownParty).toDouble()
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
Response.ok().entity(summary).build()
|
Response.ok().entity(summary).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,28 +235,23 @@ class PortfolioApi(val rpc: CordaRPCOps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ApiParty(val id: String, val text: String)
|
||||||
|
data class AvailableParties(val self: ApiParty, val counterparties: List<ApiParty>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the identity of the current node as well as a list of other counterparties that it is aware of.
|
* Returns the identity of the current node as well as a list of other counterparties that it is aware of.
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("whoami")
|
@Path("whoami")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
fun getWhoAmI(): Any {
|
fun getWhoAmI(): AvailableParties {
|
||||||
val counterParties = rpc.networkMapUpdates().first.filter { it.legalIdentity.name != "NetworkMapService" && it.legalIdentity.name != "Notary" && it.legalIdentity.name != ownParty.name }
|
val counterParties = rpc.networkMapUpdates().first.filter {
|
||||||
return json {
|
it.legalIdentity.name != "NetworkMapService" && it.legalIdentity.name != "Notary" && it.legalIdentity.name != ownParty.name
|
||||||
obj(
|
|
||||||
"self" to obj(
|
|
||||||
"id" to ownParty.owningKey.toBase58String(),
|
|
||||||
"text" to ownParty.name
|
|
||||||
),
|
|
||||||
"counterparties" to counterParties.map {
|
|
||||||
obj(
|
|
||||||
"id" to it.legalIdentity.owningKey.toBase58String(),
|
|
||||||
"text" to it.legalIdentity.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return AvailableParties(
|
||||||
|
self = ApiParty(ownParty.owningKey.toBase58String(), ownParty.name),
|
||||||
|
counterparties = counterParties.map { ApiParty(it.legalIdentity.owningKey.toBase58String(), it.legalIdentity.name) })
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ValuationCreationParams(val valuationDate: LocalDate)
|
data class ValuationCreationParams(val valuationDate: LocalDate)
|
||||||
|
@ -15,7 +15,16 @@ import java.time.LocalDate
|
|||||||
* API JSON generation functions for larger JSON outputs.
|
* API JSON generation functions for larger JSON outputs.
|
||||||
*/
|
*/
|
||||||
class PortfolioApiUtils(private val ownParty: Party) {
|
class PortfolioApiUtils(private val ownParty: Party) {
|
||||||
fun createValuations(state: PortfolioState, portfolio: Portfolio): Any {
|
data class InitialMarginView(val baseCurrency: String, val post: Map<String, Double>, val call: Map<String, Double>, val agreed: Boolean)
|
||||||
|
data class ValuationsView(
|
||||||
|
val businessDate: LocalDate,
|
||||||
|
val portfolio: Map<String, Any>,
|
||||||
|
val marketData: Map<String, Any>,
|
||||||
|
val sensitivities: Map<String, Any>,
|
||||||
|
val initialMargin: InitialMarginView,
|
||||||
|
val confirmation: Map<String, Any>)
|
||||||
|
|
||||||
|
fun createValuations(state: PortfolioState, portfolio: Portfolio): ValuationsView {
|
||||||
val valuation = state.valuation!!
|
val valuation = state.valuation!!
|
||||||
|
|
||||||
val currency = if (portfolio.trades.isNotEmpty()) {
|
val currency = if (portfolio.trades.isNotEmpty()) {
|
||||||
@ -32,141 +41,137 @@ class PortfolioApiUtils(private val ownParty: Party) {
|
|||||||
|
|
||||||
val completeSubgroups = subgroups.mapValues { it.value.mapValues { it.value[0].third.toDouble() }.toSortedMap() }
|
val completeSubgroups = subgroups.mapValues { it.value.mapValues { it.value[0].third.toDouble() }.toSortedMap() }
|
||||||
|
|
||||||
val yieldCurves = json {
|
val yieldCurves = mapOf(
|
||||||
obj(
|
"name" to "EUR",
|
||||||
"name" to "EUR",
|
"values" to completeSubgroups.get("EUR")!!.filter { !it.key.contains("Fixing") }.map {
|
||||||
"values" to completeSubgroups.get("EUR")!!.filter { !it.key.contains("Fixing") }.map {
|
mapOf(
|
||||||
json {
|
"tenor" to it.key,
|
||||||
obj(
|
"rate" to it.value
|
||||||
"tenor" to it.key,
|
)
|
||||||
"rate" to it.value
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
val fixings = mapOf(
|
||||||
)
|
"name" to "EUR",
|
||||||
}
|
"values" to completeSubgroups.get("EUR")!!.filter { it.key.contains("Fixing") }.map {
|
||||||
val fixings = json {
|
mapOf(
|
||||||
obj(
|
"tenor" to it.key,
|
||||||
"name" to "EUR",
|
"rate" to it.value
|
||||||
"values" to completeSubgroups.get("EUR")!!.filter { it.key.contains("Fixing") }.map {
|
)
|
||||||
json {
|
}
|
||||||
obj(
|
)
|
||||||
"tenor" to it.key,
|
|
||||||
"rate" to it.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val processedSensitivities = valuation.totalSensivities.sensitivities.map { it.marketDataName to it.parameterMetadata.map { it.label }.zip(it.sensitivity.toList()).toMap() }.toMap()
|
val processedSensitivities = valuation.totalSensivities.sensitivities.map { it.marketDataName to it.parameterMetadata.map { it.label }.zip(it.sensitivity.toList()).toMap() }.toMap()
|
||||||
|
|
||||||
return json {
|
val initialMarginView = InitialMarginView(
|
||||||
obj(
|
baseCurrency = currency,
|
||||||
"businessDate" to LocalDate.now(),
|
post = mapOf(
|
||||||
"portfolio" to obj(
|
"IRFX" to valuation.margin.first,
|
||||||
"trades" to tradeCount,
|
"commodity" to 0.0,
|
||||||
"baseCurrency" to currency,
|
"equity" to 0.0,
|
||||||
"IRFX" to tradeCount,
|
"credit" to 0.0,
|
||||||
"commodity" to 0,
|
"total" to valuation.margin.first
|
||||||
"equity" to 0,
|
),
|
||||||
"credit" to 0,
|
call = mapOf(
|
||||||
"total" to tradeCount,
|
"IRFX" to valuation.margin.first,
|
||||||
"agreed" to true
|
"commodity" to 0.0,
|
||||||
),
|
"equity" to 0.0,
|
||||||
"marketData" to obj(
|
"credit" to 0.0,
|
||||||
"yieldCurves" to yieldCurves,
|
"total" to valuation.margin.first
|
||||||
"fixings" to fixings,
|
),
|
||||||
"agreed" to true
|
agreed = true)
|
||||||
),
|
|
||||||
"sensitivities" to obj("curves" to processedSensitivities,
|
return ValuationsView(
|
||||||
"currency" to valuation.currencySensitivies.amounts.toList().map {
|
businessDate = LocalDate.now(),
|
||||||
obj(
|
portfolio = mapOf(
|
||||||
"currency" to it.currency.code,
|
"trades" to tradeCount,
|
||||||
"amount" to it.amount
|
"baseCurrency" to currency,
|
||||||
)
|
"IRFX" to tradeCount,
|
||||||
},
|
"commodity" to 0,
|
||||||
"agreed" to true
|
"equity" to 0,
|
||||||
),
|
"credit" to 0,
|
||||||
"initialMargin" to obj(
|
"total" to tradeCount,
|
||||||
"baseCurrency" to currency,
|
"agreed" to true
|
||||||
"post" to obj(
|
),
|
||||||
"IRFX" to valuation.margin.first,
|
marketData = mapOf(
|
||||||
"commodity" to 0,
|
"yieldCurves" to yieldCurves,
|
||||||
"equity" to 0,
|
"fixings" to fixings,
|
||||||
"credit" to 0,
|
"agreed" to true
|
||||||
"total" to valuation.margin.first
|
),
|
||||||
),
|
sensitivities = mapOf("curves" to processedSensitivities,
|
||||||
"call" to obj(
|
"currency" to valuation.currencySensitivies.amounts.toList().map {
|
||||||
"IRFX" to valuation.margin.first,
|
mapOf(
|
||||||
"commodity" to 0,
|
"currency" to it.currency.code,
|
||||||
"equity" to 0,
|
"amount" to it.amount
|
||||||
"credit" to 0,
|
)
|
||||||
"total" to valuation.margin.first
|
},
|
||||||
),
|
"agreed" to true
|
||||||
"agreed" to true
|
),
|
||||||
),
|
initialMargin = initialMarginView,
|
||||||
"confirmation" to obj(
|
confirmation = mapOf(
|
||||||
"hash" to state.hash().toString(),
|
"hash" to state.hash().toString(),
|
||||||
"agreed" to true
|
"agreed" to true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTradeView(state: IRSState): Any {
|
data class TradeView(
|
||||||
|
val fixedLeg: Map<String, Any>,
|
||||||
|
val floatingLeg: Map<String, Any>,
|
||||||
|
val common: Map<String, Any>,
|
||||||
|
val ref: String)
|
||||||
|
|
||||||
|
fun createTradeView(state: IRSState): TradeView {
|
||||||
val trade = if (state.buyer.name == ownParty.name) state.swap.toFloatingLeg() else state.swap.toFloatingLeg()
|
val trade = if (state.buyer.name == ownParty.name) state.swap.toFloatingLeg() else state.swap.toFloatingLeg()
|
||||||
val fixedLeg = trade.product.legs.first { it.type == SwapLegType.FIXED } as RateCalculationSwapLeg
|
val fixedLeg = trade.product.legs.first { it.type == SwapLegType.FIXED } as RateCalculationSwapLeg
|
||||||
val floatingLeg = trade.product.legs.first { it.type != SwapLegType.FIXED } as RateCalculationSwapLeg
|
val floatingLeg = trade.product.legs.first { it.type != SwapLegType.FIXED } as RateCalculationSwapLeg
|
||||||
val fixedRate = fixedLeg.calculation as FixedRateCalculation
|
val fixedRate = fixedLeg.calculation as FixedRateCalculation
|
||||||
val floatingRate = floatingLeg.calculation as IborRateCalculation
|
val floatingRate = floatingLeg.calculation as IborRateCalculation
|
||||||
|
|
||||||
return json {
|
return TradeView(
|
||||||
obj(
|
fixedLeg = mapOf(
|
||||||
"fixedLeg" to obj(
|
"fixedRatePayer" to state.buyer.name,
|
||||||
"fixedRatePayer" to state.buyer.name,
|
"notional" to mapOf(
|
||||||
"notional" to obj(
|
"token" to fixedLeg.currency.code,
|
||||||
"token" to fixedLeg.currency.code,
|
"quantity" to fixedLeg.notionalSchedule.amount.initialValue
|
||||||
"quantity" to fixedLeg.notionalSchedule.amount.initialValue
|
),
|
||||||
),
|
"paymentFrequency" to fixedLeg.paymentSchedule.paymentFrequency.toString(),
|
||||||
"paymentFrequency" to fixedLeg.paymentSchedule.paymentFrequency.toString(),
|
"effectiveDate" to fixedLeg.startDate.unadjusted,
|
||||||
"effectiveDate" to fixedLeg.startDate.unadjusted,
|
"terminationDate" to fixedLeg.endDate.unadjusted,
|
||||||
"terminationDate" to fixedLeg.endDate.unadjusted,
|
"fixedRate" to mapOf(
|
||||||
"fixedRate" to obj(
|
"value" to fixedRate.rate.initialValue
|
||||||
"value" to fixedRate.rate.initialValue
|
),
|
||||||
),
|
"paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name,
|
||||||
"paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name,
|
"calendar" to listOf("TODO"),
|
||||||
"calendar" to arr("TODO"),
|
"paymentCalendar" to mapOf<String, Any>() // TODO
|
||||||
"paymentCalendar" to obj() // TODO
|
),
|
||||||
),
|
floatingLeg = mapOf(
|
||||||
"floatingLeg" to obj(
|
"floatingRatePayer" to state.seller.name,
|
||||||
"floatingRatePayer" to state.seller.name,
|
"notional" to mapOf(
|
||||||
"notional" to obj(
|
"token" to floatingLeg.currency.code,
|
||||||
"token" to floatingLeg.currency.code,
|
"quantity" to floatingLeg.notionalSchedule.amount.initialValue
|
||||||
"quantity" to floatingLeg.notionalSchedule.amount.initialValue
|
),
|
||||||
),
|
"paymentFrequency" to floatingLeg.paymentSchedule.paymentFrequency.toString(),
|
||||||
"paymentFrequency" to floatingLeg.paymentSchedule.paymentFrequency.toString(),
|
"effectiveDate" to floatingLeg.startDate.unadjusted,
|
||||||
"effectiveDate" to floatingLeg.startDate.unadjusted,
|
"terminationDate" to floatingLeg.endDate.unadjusted,
|
||||||
"terminationDate" to floatingLeg.endDate.unadjusted,
|
"index" to floatingRate.index.name,
|
||||||
"index" to floatingRate.index.name,
|
"paymentRule" to floatingLeg.paymentSchedule.paymentRelativeTo,
|
||||||
"paymentRule" to floatingLeg.paymentSchedule.paymentRelativeTo,
|
"calendar" to listOf("TODO"),
|
||||||
"calendar" to arr("TODO"),
|
"paymentCalendar" to listOf("TODO"),
|
||||||
"paymentCalendar" to arr("TODO"),
|
"fixingCalendar" to mapOf<String, Any>() // TODO
|
||||||
"fixingCalendar" to obj() // TODO
|
),
|
||||||
),
|
common = mapOf(
|
||||||
"common" to obj(
|
"valuationDate" to trade.product.startDate.unadjusted,
|
||||||
"valuationDate" to trade.product.startDate.unadjusted,
|
"hashLegalDocs" to state.contract.legalContractReference.toString(),
|
||||||
"hashLegalDocs" to state.contract.legalContractReference.toString(),
|
"interestRate" to mapOf(
|
||||||
"interestRate" to obj(
|
"name" to "TODO",
|
||||||
"name" to "TODO",
|
"oracle" to "TODO",
|
||||||
"oracle" to "TODO",
|
"tenor" to mapOf(
|
||||||
"tenor" to obj(
|
"name" to "TODO"
|
||||||
"name" to "TODO"
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
ref = trade.info.id.get().value
|
||||||
"ref" to trade.info.id.get().value
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,12 @@ class HttpApi(val root: URL) {
|
|||||||
*/
|
*/
|
||||||
fun postJson(path: String, data: Any = Unit) = HttpUtils.postJson(URL(root, path), toJson(data))
|
fun postJson(path: String, data: Any = Unit) = HttpUtils.postJson(URL(root, path), toJson(data))
|
||||||
|
|
||||||
private fun toJson(any: Any) = if (any is String) any else ObjectMapper().writeValueAsString(any)
|
/**
|
||||||
|
* Send a GET request to the path on the API specified.
|
||||||
|
*/
|
||||||
|
inline fun<reified T: Any> getJson(path: String, params: Map<String, String> = mapOf()) = HttpUtils.getJson<T>(URL(root, path), params)
|
||||||
|
|
||||||
|
private fun toJson(any: Any) = any as? String ?: HttpUtils.defaultMapper.writeValueAsString(any)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi
|
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package net.corda.testing.http
|
package net.corda.testing.http
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.node.utilities.JsonSupport
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -15,6 +18,9 @@ object HttpUtils {
|
|||||||
.connectTimeout(5, TimeUnit.SECONDS)
|
.connectTimeout(5, TimeUnit.SECONDS)
|
||||||
.readTimeout(60, TimeUnit.SECONDS).build()
|
.readTimeout(60, TimeUnit.SECONDS).build()
|
||||||
}
|
}
|
||||||
|
val defaultMapper: ObjectMapper by lazy {
|
||||||
|
ObjectMapper().registerModule(JsonSupport.javaTimeModule).registerModule(KotlinModule())
|
||||||
|
}
|
||||||
|
|
||||||
fun putJson(url: URL, data: String) : Boolean {
|
fun putJson(url: URL, data: String) : Boolean {
|
||||||
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
|
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
|
||||||
@ -26,12 +32,19 @@ object HttpUtils {
|
|||||||
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
|
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun<reified T: Any> getJson(url: URL, params: Map<String, String> = mapOf()) : T {
|
||||||
|
val paramString = if(params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&")
|
||||||
|
val parameterisedUrl = URL(url.toExternalForm() + paramString)
|
||||||
|
return defaultMapper.readValue(parameterisedUrl, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
private fun makeRequest(request: Request): Boolean {
|
private fun makeRequest(request: Request): Boolean {
|
||||||
val response = client.newCall(request).execute()
|
val response = client.newCall(request).execute()
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
logger.error("Could not fulfill HTTP request of type ${request.method()} to ${request.url()}. Status Code: ${response.code()}. Message: ${response.body().string()}")
|
logger.error("Could not fulfill HTTP request of type ${request.method()} to ${request.url()}. Status Code: ${response.code()}. Message: ${response.body().string()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.isSuccessful
|
return response.isSuccessful
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user