Merge remote-tracking branch 'remotes/origin/master' into mnesbit-cor-261-artemis-over-ssl

This commit is contained in:
Matthew Nesbit 2016-07-28 13:28:31 +01:00
commit 09c795e341
32 changed files with 1123 additions and 16 deletions

View File

@ -12,6 +12,13 @@ interface CordaPluginRegistry {
*/
val webApis: List<Class<*>>
/**
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
*/
val staticServeDirs: Map<String, String>
/**
* A Map with an entry for each consumed protocol used by the webAPIs.
* The key of each map entry should contain the ProtocolLogic<T> class name.

View File

@ -8,6 +8,7 @@ so far. We have:
how this works in :doc:`protocol-state-machines`.
2. The IRS demo, which shows two nodes establishing an interest rate swap between them and performing fixings with a
rates oracle, all driven via the HTTP API.
3. The IRS demo web interface - a web interface to the IRS demo.
The demos create node data directories in the root of the project. If something goes wrong with them, blow away the
directories and try again.
@ -29,7 +30,7 @@ Open two terminals, and in the first run:
**Other**::
Other: ./gradlew installDist && ./build/install/r3prototyping/bin/trader-demo --role=BUYER
./gradlew installDist && ./build/install/r3prototyping/bin/trader-demo --role=BUYER
It will compile things, if necessary, then create a directory named trader-demo/buyer with a bunch of files inside and
start the node. You should see it waiting for a trade to begin.
@ -96,3 +97,53 @@ can see the other terminals whilst you run this command!:
**Other**::
./build/install/r3prototyping/bin/irsdemo --role=Date 2017-01-30
IRS web demo
------------
To install the web demo please follow these steps;
1. Install Node: https://nodejs.org/en/download/ and ensure the npm executable is on your classpath
2. Open a terminal
3. Run `npm install -g bower` or `sudo npm install -g bower` if on a *nix system.
4. In the terminal navigate to `<corda>/src/main/resources/com/r3corda/demos/irswebdemo`
5. Run `bower install`
To run the web demo, run the first two steps from the IRS Demo:
Open two terminals and in the first:
**Windows**::
gradlew.bat installDist & .\build\install\r3prototyping\bin\irsdemo.bat --role=NodeA
**Other**::
./gradlew installDist && ./build/install/r3prototyping/bin/irsdemo --role=NodeA
And in the second run:
**Windows**::
.\build\install\r3prototyping\bin\irsdemo.bat --role=NodeB
**Other**::
./build/install/r3prototyping/bin/irsdemo --role=NodeB
Now open your web browser to this URL:
.. note:: If using a custom node port address or port those must be used instead.
**Node A**:
http://localhost:31338/web/irsdemo
**Node B**:
http://localhost:31340/web/irsdemo
To use the demos click the "Create Deal" button, fill in the form, then click the "Submit" button. Now you will be
able to use the time controls at the top left of the home page to run the fixings. Click any individual trade in the
blotter to view it.

View File

@ -16,8 +16,13 @@ import com.r3corda.node.servlets.Config
import com.r3corda.node.servlets.DataUploadServlet
import com.r3corda.node.servlets.ResponseFilter
import com.r3corda.node.utilities.AffinityExecutor
import org.eclipse.jetty.server.Handler
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.DefaultHandler
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.server.handler.HandlerList
import org.eclipse.jetty.server.handler.ResourceHandler
import org.eclipse.jetty.servlet.DefaultServlet
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.webapp.WebAppContext
@ -96,7 +101,15 @@ class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort,
}
// API, data upload and download to services (attachments, rates oracles etc)
handlerCollection.addHandler(ServletContextHandler().apply {
handlerCollection.addHandler(buildServletContextHandler())
server.handler = handlerCollection
server.start()
return server
}
private fun buildServletContextHandler(): ServletContextHandler {
return ServletContextHandler().apply {
contextPath = "/"
setAttribute("node", this@Node)
addServlet(DataUploadServlet::class.java, "/upload/*")
@ -115,6 +128,16 @@ class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort,
resourceConfig.register(customAPI)
}
val staticDirMaps = pluginRegistries.map { x -> x.staticServeDirs }
val staticDirs = staticDirMaps.flatMap { it.keys }.zip(staticDirMaps.flatMap { it.values })
staticDirs.forEach {
val staticDir = ServletHolder(DefaultServlet::class.java)
staticDir.setInitParameter("resourceBase", it.second)
staticDir.setInitParameter("dirAllowed", "true")
staticDir.setInitParameter("pathInfoOnly", "true")
addServlet(staticDir, "/web/${it.first}/*")
}
// Give the app a slightly better name in JMX rather than a randomly generated one and enable JMX
resourceConfig.addProperties(mapOf(ServerProperties.APPLICATION_NAME to "node.api",
ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED to "true"))
@ -123,11 +146,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort,
val jerseyServlet = ServletHolder(container)
addServlet(jerseyServlet, "/api/*")
jerseyServlet.initOrder = 0 // Initialise at server start
})
server.handler = handlerCollection
server.start()
return server
}
}
override fun start(): Node {

View File

@ -102,7 +102,7 @@ object NodeInterestRates {
class FixingServicePlugin : CordaPluginRegistry {
override val webApis: List<Class<*>> = emptyList()
override val requiredProtocols: Map<String, Set<String>> = mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name)))
override val staticServeDirs: Map<String, String> = emptyMap()
}
// File upload support

View File

@ -68,6 +68,10 @@ sealed class FiberRequest(val topic: String,
override fun toString(): String {
return "Expecting response via topic ${receiveTopic} of type ${responseTypeName}"
}
// We have to do an unchecked cast, but unless the serialized form is damaged, this was
// correct when the request was instantiated
@Suppress("UNCHECKED_CAST")
val responseType: Class<R>
get() = Class.forName(responseTypeName) as Class<R>
}

View File

@ -109,9 +109,10 @@ class ProtocolStateMachineImpl<R>(val logic: ProtocolLogic<R>,
try {
suspendAction(with)
} catch (t: Throwable) {
// Do not throw exception again - Quasar completely bins it.
logger.warn("Captured exception which was swallowed by Quasar", t)
// TODO to throw or not to throw, that is the question
throw t
actionOnEnd()
_resultFuture?.setException(t)
}
}
}

View File

@ -96,7 +96,7 @@ class PerFileCheckpointStorageTests {
private var checkpointCount = 1
private val request = FiberRequest.ExpectingResponse("topic", null, random63BitValue(), random63BitValue(), null,
java.lang.String::class.java)
kotlin.String::class.java)
private fun newCheckpoint() = Checkpoint(SerializedBytes(Ints.toByteArray(checkpointCount++)), request)
}

View File

@ -260,6 +260,7 @@ object CliParamsSpec {
class IRSDemoPluginRegistry : CordaPluginRegistry {
override val webApis: List<Class<*>> = listOf(InterestRateSwapAPI::class.java)
override val staticServeDirs: Map<String, String> = mapOf("irsdemo" to javaClass.getResource("irswebdemo").toExternalForm())
override val requiredProtocols: Map<String, Set<String>> = mapOf(
Pair(AutoOfferProtocol.Requester::class.java.name, setOf(InterestRateSwap.State::class.java.name)),
Pair(UpdateBusinessDayProtocol.Broadcast::class.java.name, setOf(java.time.LocalDate::class.java.name)),
@ -404,7 +405,6 @@ private fun startNode(params: CliParams.RunNode, networkMap: SingleMessageRecipi
val node = logElapsedTime("Node startup", log) {
Node(params.dir, params.networkAddress, params.apiAddress, config, networkMapId, advertisedServices, DemoClock()).start()
}
// TODO: This should all be replaced by the identity service being updated
// as the network map changes.
for (identityFile in params.tradeWithIdentities) {

View File

@ -2,18 +2,23 @@ package com.r3corda.demos.api
import com.r3corda.contracts.InterestRateSwap
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.failure
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.linearHeadsOfType
import com.r3corda.core.success
import com.r3corda.core.utilities.loggerFor
import com.r3corda.demos.protocols.AutoOfferProtocol
import com.r3corda.demos.protocols.ExitServerProtocol
import com.r3corda.demos.protocols.UpdateBusinessDayProtocol
import org.apache.commons.io.IOUtils
import java.net.URI
import java.net.URLConnection
import java.time.LocalDate
import java.time.LocalDateTime
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.*
import java.nio.channels.*
import java.util.concurrent.TimeUnit
/**
* This provides a simplified API, currently for demonstration use only.
@ -65,8 +70,13 @@ class InterestRateSwapAPI(val services: ServiceHub) {
@Path("deals")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDeal(newDeal: InterestRateSwap.State): Response {
services.invokeProtocolAsync<SignedTransaction>(AutoOfferProtocol.Requester::class.java, newDeal).get()
return Response.created(URI.create(generateDealLink(newDeal))).build()
try {
services.invokeProtocolAsync<SignedTransaction>(AutoOfferProtocol.Requester::class.java, newDeal).get()
return Response.created(URI.create(generateDealLink(newDeal))).build()
} catch (ex: Throwable) {
logger.info("Exception when creating deal: ${ex.toString()}")
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.toString()).build()
}
}
@GET

View File

@ -0,0 +1,3 @@
{
"directory": "js/bower_components"
}

View File

@ -0,0 +1,2 @@
bower_components

View File

@ -0,0 +1,25 @@
{
"name": "www",
"description": "",
"main": "",
"license": "MIT",
"homepage": "",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular": "^1.5.6",
"jquery": "^3.0.0",
"angular-route": "^1.5.7",
"lodash": "^4.13.1",
"angular-fcsa-number": "^1.5.3",
"jquery.maskedinput": "^1.4.1",
"requirejs": "^2.2.0",
"semantic": "semantic-ui#^2.2.2"
}
}

View File

@ -0,0 +1,15 @@
#fixedleg tbody tr:nth-child(odd) {
background-color: #EEFAEE;
}
#createfixedleg, #fixedleg tbody tr:nth-child(even), #fixedleg thead th {
background-color: #D0FAD0;
}
#floatingleg tbody tr:nth-child(odd) {
background-color: #FAEEEE;
}
#createfloatingleg, #floatingleg tbody tr:nth-child(even), #floatingleg thead th {
background-color: #FAD0D0;
}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<!-- Standard Meta -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<!-- Site Properties -->
<title>IRS Demo Viewer</title>
<link rel="shortcut icon" type="image/x-icon" href="http://r3cev.com/favicon.ico" />
<link rel="stylesheet" type="text/css" href="js/bower_components/semantic/dist/semantic.css" />
<link rel="stylesheet" type="text/css" href="css/main.css" />
<script data-main="js/require-config" src="js/bower_components/requirejs/require.js"></script>
</head>
<body ng-controller="HomeController">
<div class="ui attached inverted menu">
<span class="header item"><a href="#/">IRS Web Demo</a></span>
<span class="item"><a href="#/">Recent Deals</a></span>
<span class="item"><a href="#/create-deal">Create Deal</a></span>
</div>
<div class="ui container">
<div id="loading" class="ui modal">
<div class="ui text loader">Loading</div>
</div>
<div ng-view></div>
</div>
</body>
</html>

View File

@ -0,0 +1,79 @@
"use strict"
define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
let calculationModel = {
expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
floatingLegPaymentSchedule: {
},
fixedLegPaymentSchedule: {
}
};
let indexLookup = {
"GBP": "ICE LIBOR",
"USD": "ICE LIBOR",
"EUR": "EURIBOR"
};
let calendarLookup = {
"GBP": "London",
"USD": "NewYork",
"EUR": "London"
};
let Deal = function(dealViewModel) {
let now = new Date();
let tradeId = `T${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getUTCHours()}:${now.getUTCMinutes()}:${now.getUTCSeconds()}:${now.getUTCMilliseconds()}`
this.toJson = () => {
let fixedLeg = {};
let floatingLeg = {};
let common = {};
_.assign(fixedLeg, dealViewModel.fixedLeg);
_.assign(floatingLeg, dealViewModel.floatingLeg);
_.assign(common, dealViewModel.common);
_.assign(fixedLeg.fixedRate, fixedRateViewModel);
fixedLeg.fixedRate = Number(fixedLeg.fixedRate) / 100;
fixedLeg.notional.token = common.baseCurrency;
fixedLeg.effectiveDate = formatDateForNode(common.effectiveDate);
fixedLeg.terminationDate = formatDateForNode(common.terminationDate);
fixedLeg.fixedRate = { ratioUnit: { value: fixedLeg.fixedRate } };
fixedLeg.dayCountBasisDay = fixedLeg.dayCountBasis.day;
fixedLeg.dayCountBasisYear = fixedLeg.dayCountBasis.year;
fixedLeg.paymentCalendar = calendarLookup[common.baseCurrency];
delete fixedLeg.dayCountBasis;
floatingLeg.notional.token = common.baseCurrency;
floatingLeg.effectiveDate = formatDateForNode(common.effectiveDate);
floatingLeg.terminationDate = formatDateForNode(common.terminationDate);
floatingLeg.dayCountBasisDay = floatingLeg.dayCountBasis.day;
floatingLeg.dayCountBasisYear = floatingLeg.dayCountBasis.year;
floatingLeg.index = indexLookup[common.baseCurrency];
floatingLeg.fixingCalendar = [calendarLookup[common.baseCurrency]];
floatingLeg.paymentCalendar = [calendarLookup[common.baseCurrency]];
delete floatingLeg.dayCountBasis;
common.tradeID = tradeId;
common.eligibleCurrency = common.baseCurrency;
common.independentAmounts.token = common.baseCurrency;
common.threshold.token = common.baseCurrency;
common.minimumTransferAmount.token = common.baseCurrency;
common.rounding.token = common.baseCurrency;
delete common.effectiveDate;
delete common.terminationDate;
let json = {
fixedLeg: fixedLeg,
floatingLeg: floatingLeg,
calculation: calculationModel,
common: common
}
return json;
};
};
return Deal;
})

View File

@ -0,0 +1,24 @@
"use strict"
function formatDateForNode(date) {
// Produces yyyy-dd-mm. JS is missing proper date formatting libs
let day = ("0" + (date.getDate())).slice(-2);
let month = ("0" + (date.getMonth() + 1)).slice(-2);
return `${date.getFullYear()}-${month}-${day}`;
}
function formatDateForAngular(dateStr) {
let parts = dateStr.split("-");
return new Date(parts[0], parts[1], parts[2]);
}
define([
'angular',
'angularRoute',
'jquery',
'fcsaNumber',
'semantic'
], (angular, angularRoute, $, fcsaNumber, semantic) => {
angular.module('irsViewer', ['ngRoute', 'fcsa-number']);
requirejs(['routes']);
});

View File

@ -0,0 +1,36 @@
'use strict';
define([
'angular',
'maskedInput',
'utils/semantic',
'utils/dayCountBasisLookup',
'services/NodeApi',
'Deal'
], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => {
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService) {
semantic.init($scope, nodeService.isLoading);
$scope.dayCountBasisLookup = dayCountBasisLookup;
$scope.deal = nodeService.newDeal();
$scope.createDeal = () => {
nodeService.createDeal(new Deal($scope.deal))
.then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => {
$scope.formError = resp.data;
});
};
$('input.percent').mask("9.999999%", {placeholder: "", autoclear: false});
$('#swapirscolumns').click(() => {
let first = $('#irscolumns .irscolumn:eq( 0 )');
let last = $('#irscolumns .irscolumn:eq( 1 )');
first.before(last);
let swapPayers = () => {
let tmp = $scope.deal.floatingLeg.floatingRatePayer;
$scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer;
$scope.deal.fixedLeg.fixedRatePayer = tmp;
};
$scope.$apply(swapPayers);
});
});
});

View File

@ -0,0 +1,9 @@
'use strict';
define(['angular', 'utils/semantic', 'services/NodeApi'], (angular, semantic, nodeApi) => {
angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService) {
semantic.init($scope, nodeService.isLoading);
nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = deal);
});
});

View File

@ -0,0 +1,23 @@
'use strict';
define(['angular', 'utils/semantic', 'services/NodeApi'], (angular, semantic, nodeApi) => {
angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService) {
semantic.addLoadingModal($scope, nodeService.isLoading);
let handleHttpFail = (resp) => {
$scope.httpError = resp.data
}
$scope.infoMsg = "";
$scope.errorText = "";
$scope.date = { "year": "...", "month": "...", "day": "..." };
$scope.updateDate = (type) => {
nodeService.updateDate(type).then((newDate) => {
$scope.date = newDate
}, handleHttpFail);
};
nodeService.getDate().then((date) => $scope.date = date);
nodeService.getDeals().then((deals) => $scope.deals = deals);
});
})

View File

@ -0,0 +1,28 @@
'use strict';
require.config({
paths: {
angular: 'bower_components/angular/angular',
angularRoute: 'bower_components/angular-route/angular-route',
fcsaNumber: 'bower_components/angular-fcsa-number/src/fcsaNumber',
jquery: 'bower_components/jquery/dist/jquery',
semantic: 'bower_components/semantic/dist/semantic',
lodash: 'bower_components/lodash/lodash',
maskedInput: 'bower_components/jquery.maskedinput/dist/jquery.maskedinput'
},
shim: {
'angular' : {'exports' : 'angular'},
'angularRoute': ['angular'],
'fcsaNumber': ['angular'],
'semantic': ['jquery'],
'maskedInput': ['jquery']
},
priority: [
"angular"
],
baseUrl: 'js',
});
require(['angular', 'app'], (angular, app) => {
});

View File

@ -0,0 +1,33 @@
'use strict';
define([
'angular',
'controllers/Home',
'controllers/Deal',
'controllers/CreateDeal'
], (angular) => {
angular.module('irsViewer').config(($routeProvider, $locationProvider) => {
$routeProvider
.when('/', {
controller: 'HomeController',
templateUrl: 'view/home.html'
})
.when('/deal/:dealId', {
controller: 'DealController',
templateUrl: 'view/deal.html'
})
.when('/party/:partyId', {
templateUrl: 'view/party.html'
})
.when('/create-deal', {
controller: 'CreateDealController',
templateUrl: 'view/create-deal.html'
})
.otherwise({redirectTo: '/'});
});
angular.element().ready(function() {
// bootstrap the app manually
angular.bootstrap(document, ['irsViewer']);
});
});

View File

@ -0,0 +1,99 @@
'use strict';
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
angular.module('irsViewer').factory('nodeService', ($http) => {
return new (function() {
let date = new Date(2016, 0, 1, 0, 0, 0);
let curLoading = {};
let load = (type, promise) => {
curLoading[type] = true;
return promise.then((arg) => {
curLoading[type] = false;
return arg;
}, (arg) => {
curLoading[type] = false;
throw arg;
});
}
let changeDateOnNode = (newDate) => {
const dateStr = formatDateForNode(newDate);
let endpoint = '/api/irs/demodate';
return load('date', $http.put(endpoint, "\"" + dateStr + "\"")).then((resp) => {
date = newDate;
return this.getDateModel(date);
});
}
this.getDate = () => {
return load('date', $http.get('/api/irs/demodate')).then((resp) => {
const parts = resp.data.split("-");
date = new Date(parts[0], parts[1] - 1, parts[2]); // JS uses 0 based months
return this.getDateModel(date);
});
}
this.updateDate = (type) => {
let newDate = date;
switch(type) {
case "year":
newDate.setFullYear(date.getFullYear() + 1);
break;
case "month":
newDate.setMonth(date.getMonth() + 1);
break;
case "day":
newDate.setDate(date.getDate() + 1);
break;
}
return changeDateOnNode(newDate);
};
this.getDeals = () => {
return load('deals', $http.get('/api/irs/deals')).then((resp) => {
return resp.data.reverse();
});
};
this.getDeal = (dealId) => {
return load('deal' + dealId, $http.get('/api/irs/deals/' + dealId)).then((resp) => {
// Do some data modification to simplify the model
let deal = resp.data;
deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6);
return deal;
});
};
this.getDateModel = (date) => {
return {
"year": date.getFullYear(),
"month": date.getMonth() + 1, // JS uses 0 based months
"day": date.getDate()
};
}
this.isLoading = () => {
return _.reduce(Object.keys(curLoading), (last, key) => {
return (last || curLoading[key]);
}, false);
}
this.newDeal = () => {
return dealViewModel;
}
this.createDeal = (deal) => {
return load('create-deal', $http.post('/api/irs/deals', deal.toJson()))
.then((resp) => {
return deal.tradeId;
}, (resp) => {
throw resp;
})
}
});
});
});

View File

@ -0,0 +1,34 @@
'use strict';
define([], () => {
return {
"30/360": {
"day": "D30",
"year": "Y360"
},
"30E/360": {
"day": "D30E",
"year": "Y360"
},
"ACT/360": {
"day": "DActual",
"year": "Y360"
},
"ACT/365 Fixed": {
"day": "DActual",
"year": "Y365F"
},
"ACT/365 L": {
"day": "DActual",
"year": "Y365L"
},
"ACT/ACT ISDA": {
"day": "DActual",
"year": "YISDA"
},
"ACT/ACT ICMA": {
"day": "DActual",
"year": "YICMA"
},
};
})

View File

@ -0,0 +1,22 @@
'use strict';
define(['jquery', 'semantic'], ($, semantic) => {
return {
init: function($scope, loadingFunc) {
$('.ui.accordion').accordion();
$('.ui.dropdown').dropdown();
$('.ui.sticky').sticky();
this.addLoadingModal($scope, loadingFunc);
},
addLoadingModal: ($scope, loadingFunc) => {
$scope.$watch(loadingFunc, (newVal) => {
if(newVal === true) {
$('#loading').modal('setting', 'closable', false).modal('show');
} else {
$('#loading').modal('hide');
}
});
}
};
});

View File

@ -0,0 +1,38 @@
'use strict';
define([], () => {
return {
baseCurrency: "USD",
effectiveDate: new Date(2016, 2, 11),
terminationDate: new Date(2026, 2, 11),
eligibleCreditSupport: "Cash in an Eligible Currency",
independentAmounts: {
quantity: 0
},
threshold: {
quantity: 0
},
minimumTransferAmount: {
quantity: 25000000
},
rounding: {
quantity: 1000000
},
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.token.currencyCode.equals('GBP')) ? 365 : 360",
hashLegalDocs: "put hash here"
};
});

View File

@ -0,0 +1,9 @@
'use strict';
define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fixedLeg, floatingLeg, common) => {
return {
fixedLeg: fixedLeg,
floatingLeg: floatingLeg,
common: common
};
});

View File

@ -0,0 +1,20 @@
'use strict';
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
return {
fixedRatePayer: "Bank A",
notional: {
quantity: 2500000000
},
paymentFrequency: "SemiAnnual",
effectiveDateAdjustment: null,
terminationDateAdjustment: null,
fixedRate: "1.676",
dayCountBasis: dayCountBasisLookup["ACT/360"],
rollConvention: "ModifiedFollowing",
dayInMonth: 10,
paymentRule: "InArrears",
paymentDelay: "0",
interestPeriodAdjustment: "Adjusted"
};
});

View File

@ -0,0 +1,9 @@
'use strict';
define([], () => {
return {
ratioUnit: {
value: 0.01 // %
}
};
});

View File

@ -0,0 +1,28 @@
'use strict';
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
return {
floatingRatePayer: "Bank B",
notional: {
quantity: 2500000000
},
paymentFrequency: "Quarterly",
effectiveDateAdjustment: null,
terminationDateAdjustment: null,
dayCountBasis: dayCountBasisLookup["ACT/360"],
rollConvention: "ModifiedFollowing",
fixingRollConvention: "ModifiedFollowing",
dayInMonth: 10,
resetDayInMonth: 10,
paymentRule: "InArrears",
paymentDelay: "0",
interestPeriodAdjustment: "Adjusted",
fixingPeriodOffset: 2,
resetRule: "InAdvance",
fixingsPerPayment: "Quarterly",
indexSource: "Rates Service Provider",
indexTenor: {
name: "3M"
}
};
});

View File

@ -0,0 +1,186 @@
<div class="ui container">
<div class="ui negative message" id="form-error" ng-show="formError">{{formError}}</div>
<h3 class="ui horizontal divider header">
<i class="list icon"></i>
New Deal
</h3>
<form id="deal-form" class="ui form" ng-submit="createDeal()">
<div id="irscolumns" class="ui centered grid">
<div class="sixteen wide tablet eight wide computer column">
<div class="field">
<label>Base Currency</label>
<select class="ui fluid" name="token" ng-model="deal.common.baseCurrency">
<option value="EUR">EUR</option>
<option value="USD">USD</option>
<option value="GBP">GBP</option>
</select>
</div>
<div class="field">
<label>Effective Date</label>
<input type="date" name="effectiveDate" ng-model="deal.common.effectiveDate"/>
</div>
<div class="field">
<label>Termination Date</label>
<input type="date" name="terminationDate" ng-model="deal.common.terminationDate"/>
</div>
</div>
<div class="sixteen wide column">
<button type="button" id="swapirscolumns" class="ui icon button fluid">
<i class="exchange icon"></i>
</button>
</div>
<div class="eight wide column irscolumn" id="createfixedleg">
<h3>Fixed Leg</h3>
<div class="field">
<label>Fixed Rate Payer</label>
<input type="text" name="fixedRatePayer" ng-model="deal.fixedLeg.fixedRatePayer"/>
</div>
<div class="field">
<label>Notional</label>
<input type="text" name="quantity" ng-model="deal.fixedLeg.notional.quantity" fcsa-number/>
</div>
<div class="field">
<label>Fixed Rate</label>
<input type="text" name="value" class="percent" ng-model="deal.fixedLeg.fixedRate"/>
</div>
<div class="field">
<label>Payment Frequency</label>
<select class="ui selection" ng-model="deal.fixedLeg.paymentFrequency">
<option value="Annual">Annual</option>
<option value="SemiAnnual">Semi Annual</option>
<option value="Quarterly">Quarterly</option>
</select>
</div>
<div class="field">
<label>Day Count Basis</label>
<select class="ui selection"
ng-model="deal.fixedLeg.dayCountBasis"
ng-options="key for (key, value) in dayCountBasisLookup">
</select>
</div>
<div class="field">
<label>Roll Convention</label>
<select class="ui selection" ng-model="deal.fixedLeg.rollConvention">
<option value="Following">Following</option>
<option value="Preceding">Preceding</option>
<option value="ModifiedFollowing">Modified following</option>
<option value="ModifiedFollowingBimonthly">Modified following bimonthly</option>
<option value="EndOfMonth">End of month</option>
</select>
</div>
<div class="field">
<label>Day in Month</label>
<input type="number" name="dayInMonth" min="1" max="31" ng-model="deal.fixedLeg.dayInMonth"/>
</div>
<div class="field">
<label>Payment Delay</label>
<select class="ui selection" ng-model="deal.fixedLeg.paymentDelay">
<option value="0">T+00D</option>
<option value="1">T+01D</option>
<option value="2" selected="selected">T+02D</option>
<option value="3">T+03D</option>
</select>
</div>
<div class="field">
<label>Interest Period Adjustment</label>
<select class="ui selection" ng-model="deal.fixedLeg.interestPeriodAdjustment">
<option value="Adjusted">Adjusted</option>
<option value="Unadjusted">Unadjusted</option>
</select>
</div>
</div>
<div class="eight wide column irscolumn" id="createfloatingleg">
<h3>Floating Leg</h3>
<div class="field">
<label>Floating Rate Payer</label>
<input type="text" name="floatingRatePayer" ng-model="deal.floatingLeg.floatingRatePayer"/>
</div>
<div class="field">
<label>Notional</label>
<input type="text" name="quantity" ng-model="deal.floatingLeg.notional.quantity" fcsa-number/>
</div>
<div class="field">
<label>Payment Frequency</label>
<select class="ui selection" ng-model="deal.floatingLeg.paymentFrequency">
<option value="Annual">Annual</option>
<option value="Quarterly">Quarterly</option>
<option value="SemiAnnual">Semi Annual</option>
</select>
</div>
<div class="field">
<label>Day Count Basis</label>
<select class="ui selection"
ng-model="deal.floatingLeg.dayCountBasis"
ng-options="key for (key, value) in dayCountBasisLookup">
</select>
</div>
<div class="field">
<label>Roll Convention</label>
<select class="ui selection" ng-model="deal.floatingLeg.rollConvention">
<option value="Following">Following</option>
<option value="Preceding">Preceding</option>
<option value="ModifiedFollowing">Modified following</option>
<option value="ModifiedFollowingBimonthly">Modified following bimonthly</option>
<option value="EndOfMonth">End of month</option>
</select>
</div>
<div class="field">
<label>Fixing Roll Convention</label>
<select class="ui selection" ng-model="deal.floatingLeg.fixingRollConvention">
<option value="Following">Following</option>
<option value="Preceding">Preceding</option>
<option value="ModifiedFollowing">Modified following</option>
<option value="ModifiedFollowingBimonthly">Modified following bimonthly</option>
<option value="EndOfMonth">End of month</option>
</select>
</div>
<div class="field">
<label>Day In Month</label>
<input type="number" name="dayInMonth" min="1" max="31" ng-model="deal.floatingLeg.dayInMonth"/>
</div>
<div class="field">
<label>Reset Day In Month</label>
<input type="number" name="resetDayInMonth" min="1" max="31" ng-model="deal.floatingLeg.resetDayInMonth"/>
</div>
<div class="field">
<label>Payment Delay</label>
<select class="ui selection" ng-model="deal.floatingLeg.paymentDelay">
<option value="0">T+00D</option>
<option value="1">T+01D</option>
<option value="2">T+02D</option>
<option value="3">T+03D</option>
</select>
</div>
<div class="field">
<label>Interest Period Adjustment</label>
<select class="ui selection" ng-model="deal.floatingLeg.interestPeriodAdjustment">
<option value="Adjusted">Adjusted</option>
<option value="Unadjusted">Unadjusted</option>
</select>
</div>
<div class="field">
<label>Fixing Period Offset</label>
<input type="number" min="0" name="fixingPeriodOffset" ng-model="deal.floatingLeg.fixingPeriodOffset"/>
</div>
<div class="field">
<label>Reset Rule</label>
<select class="ui selection" ng-model="deal.floatingLeg.resetRule">
<option value="InAdvance">In Advance</option>
<option value="InArrears">In Arrears</option>
</select>
</div>
<div class="field">
<label>Fixings Per Payment</label>
<select class="ui selection" ng-model="deal.floatingLeg.fixingsPerPayment">
<option value="Annual">Annual</option>
<option value="Quarterly" selected="selected">Quarterly</option>
<option value="SemiAnnual">Semi Annual</option>
</select>
</div>
</div>
<div class="sixteen wide tablet eight wide computer column">
<input type="submit" class="ui submit primary button fluid"/>
</div>
</div>
</form>
</div>

View File

@ -0,0 +1,206 @@
<div class="ui container">
<div class="ui hidden divider"></div>
<div class="ui grid">
<div class="sixteen wide column" id="common">
<table class="ui striped table">
<thead>
<tr class="center aligned">
<th colspan="2">Common Information</th>
</tr>
</thead>
<tbody>
<tr class="center aligned">
<td>Parties</td>
<td>
<span ng-repeat="party in deal.parties">
{{party}}<span ng-show="!$last">,</span>
</span>
</td>
</tr>
<tr class="center aligned">
<td>Trade ID</td>
<td>{{deal.ref}}</td>
</tr>
<tr class="center aligned">
<td>Valuation Date</td>
<td>{{deal.common.valuationDate}}</td>
</tr>
<tr class="center aligned">
<td>Legal Document Hash</td>
<td>{{deal.common.hashLegalDocs}}</td>
</tr>
<tr class="center aligned">
<td>Interest Rates</td>
<td>
{{deal.common.interestRate.name}} with tenor
{{deal.common.interestRate.tenor.name}} via oracle
{{deal.common.interestRate.oracle}}
</td>
</tr>
</tbody>
</table>
</div>
<div class="eight wide column" id="fixedleg">
<table class="ui striped table">
<thead>
<tr class="center aligned">
<th colspan="2">Fixed Leg</th>
</tr>
</thead>
<tbody>
<tr class="center aligned">
<td>Payer</td>
<td>{{deal.fixedLeg.fixedRatePayer}}</td>
</tr>
<tr class="center aligned">
<td>Notional</td>
<td>{{deal.fixedLeg.notional.quantity | number}} {{deal.fixedLeg.notional.token}}</td>
</tr>
<tr class="center aligned">
<td>Payment Frequency</td>
<td>{{deal.fixedLeg.paymentFrequency}}</td>
</tr>
<tr class="center aligned">
<td>Effective From</td>
<td>{{deal.fixedLeg.effectiveDate}}</td>
</tr>
<tr class="center aligned">
<td>Fixed Rate</td>
<td>
<span ng-show="!deal.fixedLeg.fixedRate.positive">-</span>
{{deal.fixedLeg.fixedRate.value}}%
</td>
</tr>
<tr class="center aligned">
<td>Terminates</td>
<td>{{deal.fixedLeg.terminationDate}}</td>
</tr>
<tr class="center aligned">
<td>Payment Rule</td>
<td>{{deal.fixedLeg.paymentRule}}</td>
</tr>
<tr class="center aligned">
<td>Payment Calendars</td>
<td>
<span ng-repeat="calendar in deal.fixedLeg.paymentCalendar.calendars">
<span>{{calendar}}</span><span ng-show="!$last">,</span>
</span>
</td>
</tr>
<tr class="center aligned">
<td colspan="2">
<div class="ui accordion">
<div class="title">
<i class="dropdown icon"></i>
Holiday Dates
</div>
<div class="content">
<table class="ui celled small table">
<tbody>
<tr class="center aligned" ng-repeat="date in deal.fixedLeg.paymentCalendar.holidayDates">
<td>{{date}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="eight wide column" id="floatingleg">
<table class="ui striped table">
<thead>
<tr class="center aligned">
<th colspan="2">Floating Leg</th>
</tr>
</thead>
<tbody>
<tr class="center aligned">
<td>Payer</td>
<td>{{deal.floatingLeg.floatingRatePayer}}</td>
</tr>
<tr class="center aligned">
<td>Notional</td>
<td>{{deal.floatingLeg.notional.quantity | number}} {{deal.floatingLeg.notional.token}}</td>
</tr>
<tr class="center aligned">
<td>Payment Frequency</td>
<td>{{deal.floatingLeg.paymentFrequency}}</td>
</tr>
<tr class="center aligned">
<td>Effective From</td>
<td>{{deal.floatingLeg.effectiveDate}}</td>
</tr>
<tr class="center aligned">
<td>Index</td>
<td>{{deal.floatingLeg.index}}</td>
</tr>
<tr class="center aligned">
<td>Terminates</td>
<td>{{deal.floatingLeg.terminationDate}}</td>
</tr>
<tr class="center aligned">
<td>Payment Rule</td>
<td>{{deal.floatingLeg.paymentRule}}</td>
</tr>
<tr class="center aligned">
<td>Payment Calendars</td>
<td>
<span ng-repeat="calendar in deal.floatingLeg.paymentCalendar.calendars">
<span>{{calendar}}</span><span ng-show="!$last">,</span>
</span>
</td>
</tr>
<tr class="center aligned">
<td colspan="2">
<div class="ui accordion">
<div class="title">
<i class="dropdown icon"></i>
Holiday Dates
</div>
<div class="content">
<table class="ui celled small table">
<tbody>
<tr class="center aligned" ng-repeat="date in deal.floatingLeg.paymentCalendar.holidayDates">
<td>{{date}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
<tr class="center aligned">
<td>Fixing Calendars</td>
<td>
<span ng-repeat="calendar in deal.floatingLeg.fixingCalendar.calendars">
<span>{{calendar}}</span><span ng-show="!$last">,</span>
</span>
</td>
</tr>
<tr class="center aligned">
<td colspan="2">
<div class="ui accordion">
<div class="title">
<i class="dropdown icon"></i>
Holiday Dates
</div>
<div class="content">
<table class="ui celled small table">
<tbody>
<tr class="center aligned" ng-repeat="date in deal.floatingLeg.fixingCalendar.holidayDates">
<td>{{date}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,57 @@
<div class="ui container">
<div class="ui negative message" id="http-error" ng-show="httpError">{{httpError}}</div>
<div class="ui info message" id="info-message" ng-show="infoMsg">{{infoMsg}}</div>
<div class="ui active dimmer" ng-show="isLoading()">
<div class="ui text loader">Loading</div>
</div>
<h3 class="ui horizontal divider header">
<i class="options icon"></i>
Controls
</h3>
<div class="ui card centered">
<div class="content" style="width:110%">
<div class="header">Run fixings</div>
<div class="description">
<div class="ui left labeled button">
<span class="ui basic label">{{date.year}}</span>
<button class="ui icon button" ng-click="updateDate('year')"><i class="plus icon"></i></button>
</div>
<div class="ui left labeled button">
<span class="ui basic label">{{date.month}}</span>
<button class="ui icon button" ng-click="updateDate('month')"><i class="plus icon"></i></button>
</div>
<div class="ui left labeled button">
<span class="ui basic label">{{date.day}}</span>
<button class="ui icon button" ng-click="updateDate('day')"><i class="plus icon"></i></button>
</div>
</div>
</div>
</div>
<div class="ui hidden divider"></div>
<div class="ui main">
<h3 class="ui horizontal divider header">
<i class="browser icon"></i>
Recent deals
</h3>
<table class="ui striped table">
<thead>
<tr class="center aligned">
<th>Trade Id</th>
<th>Fixed Leg Payer</th>
<th>Amount</th>
<th>Floating Rate Payer</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr class="center aligned" ng-repeat="deal in deals">
<td><a href="#/deal/{{deal.ref}}">{{deal.ref}}</a></td>
<td class="single line">{{deal.fixedLeg.fixedRatePayer}}</td>
<td class="single line">{{deal.fixedLeg.notional.quantity | number}} {{deal.fixedLeg.notional.token}}</td>
<td class="single line">{{deal.floatingLeg.floatingRatePayer}}</td>
<td class="single line">{{deal.floatingLeg.notional.quantity | number}} {{deal.floatingLeg.notional.token}}</td>
</tr>
</tbody>
</table>
</div>
</div>