├── JavaScriptApps ├── client │ ├── static │ │ ├── .gitkeep │ │ └── fonts │ │ │ └── roboto │ │ │ ├── Roboto-Bold.eot │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Light.eot │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Thin.eot │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.eot │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Regular.eot │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Thin.woff2 │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.woff │ │ │ └── Roboto-Regular.woff2 │ ├── .eslintignore │ ├── .gitignore │ ├── config │ │ ├── prod.env.js │ │ ├── dev.env.js │ │ └── index.js │ ├── .babelrc │ ├── src │ │ ├── assets │ │ │ ├── logo.png │ │ │ └── fonts │ │ │ │ └── roboto │ │ │ │ ├── Roboto-Bold.eot │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-Bold.woff │ │ │ │ ├── Roboto-Bold.woff2 │ │ │ │ ├── Roboto-Light.eot │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-Light.woff │ │ │ │ ├── Roboto-Medium.eot │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-Thin.eot │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ ├── Roboto-Thin.woff │ │ │ │ ├── Roboto-Thin.woff2 │ │ │ │ ├── Roboto-Light.woff2 │ │ │ │ ├── Roboto-Medium.woff │ │ │ │ ├── Roboto-Medium.woff2 │ │ │ │ ├── Roboto-Regular.eot │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Regular.woff │ │ │ │ └── Roboto-Regular.woff2 │ │ ├── main.js │ │ ├── components │ │ │ └── Row.vue │ │ └── App.vue │ ├── .editorconfig │ ├── build │ │ ├── dev-client.js │ │ ├── build.js │ │ ├── webpack.dev.conf.js │ │ ├── utils.js │ │ ├── dev-server.js │ │ ├── webpack.base.conf.js │ │ └── webpack.prod.conf.js │ ├── README.md │ ├── .eslintrc.js │ ├── index.html │ └── package.json └── server │ ├── static │ ├── .gitkeep │ └── fonts │ │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.woff2 │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.woff │ │ └── Roboto-Regular.woff2 │ ├── .eslintignore │ ├── .gitignore │ ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js │ ├── .babelrc │ ├── src │ ├── assets │ │ ├── logo.png │ │ └── fonts │ │ │ └── roboto │ │ │ ├── Roboto-Bold.eot │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.eot │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Medium.eot │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Thin.eot │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Thin.woff2 │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.eot │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Regular.woff │ │ │ └── Roboto-Regular.woff2 │ ├── main.js │ ├── components │ │ └── Row.vue │ └── App.vue │ ├── .editorconfig │ ├── build │ ├── dev-client.js │ ├── build.js │ ├── webpack.dev.conf.js │ ├── utils.js │ ├── dev-server.js │ ├── webpack.base.conf.js │ └── webpack.prod.conf.js │ ├── README.md │ ├── .eslintrc.js │ ├── index.html │ └── package.json ├── CounterRFQ.xlsm ├── RequestRFQ.xlsm ├── _StartHttpServer.bat ├── NOTICE ├── GlueSymphonyRfqBridge ├── IGlueRfqServer.cs ├── packages.config ├── Models │ ├── RfqResponseType.cs │ ├── RfqCommand.cs │ ├── RfqCommentSubscription.cs │ ├── ProductDetails.cs │ ├── BotCommandExtensions.cs │ ├── RfqComment.cs │ ├── BotCommand.cs │ ├── RfqQuoteRequest.cs │ ├── RfqQuoteInquiry.cs │ └── RfqQuoteInquiryResponse.cs ├── Event.cs ├── Glue │ ├── GlueRfqBridgeConfiguration.cs │ ├── GlueExtensions.cs │ ├── GlueStreamHandler.cs │ ├── Glue.cs │ └── GlueRfqBridge.cs ├── NumericExtensions.cs ├── Config │ ├── overrides.LOCAL-TICK42.properties │ └── agm.properties ├── Symphony │ ├── SymphonyRfqBridgeConfiguration.cs │ ├── SymphonyMessageExtensions.cs │ └── SymphonyRfqBridge.cs ├── Properties │ └── AssemblyInfo.cs ├── App.config ├── IGlueRfqBridge.cs ├── Program.cs └── GlueSymphonyRfqBridge.csproj ├── StartBot.cmd ├── GlueSymphonyRfqBridge.userprefs ├── GlueSymphonyRfqBridge.sln ├── README.md ├── RfqClient.md ├── RfqServer.md └── LICENSE /JavaScriptApps/client/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /JavaScriptApps/server/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /JavaScriptApps/client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /JavaScriptApps/server/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /CounterRFQ.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/CounterRFQ.xlsm -------------------------------------------------------------------------------- /JavaScriptApps/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /JavaScriptApps/server/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /RequestRFQ.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/RequestRFQ.xlsm -------------------------------------------------------------------------------- /JavaScriptApps/client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /JavaScriptApps/server/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /_StartHttpServer.bat: -------------------------------------------------------------------------------- 1 | node demos-http-server/node_modules/http-server/bin/http-server ./../ -p 22080 -a 127.0.0.1 -s 2 | -------------------------------------------------------------------------------- /JavaScriptApps/client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/logo.png -------------------------------------------------------------------------------- /JavaScriptApps/server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/logo.png -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/static/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/static/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /JavaScriptApps/client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | /* eslint-disable no-new */ 5 | new Vue({ 6 | el: '#app', 7 | render: h => h(App) 8 | }) 9 | -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /JavaScriptApps/server/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | /* eslint-disable no-new */ 5 | new Vue({ 6 | el: '#app', 7 | render: h => h(App) 8 | }) 9 | -------------------------------------------------------------------------------- /JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/client/src/assets/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symphonyoss/glue-symphony-rfq/master/JavaScriptApps/server/src/assets/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /JavaScriptApps/client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /JavaScriptApps/server/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | glue-symphony-rfq - Symphony Software Foundation 2 | Copyright (c) 2016 Tick42 3 | 4 | This product includes software developed at the Symphony Software Foundation (http://symphony.foundation). 5 | -------------------------------------------------------------------------------- /JavaScriptApps/client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /JavaScriptApps/server/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /JavaScriptApps/server/src/components/Row.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/IGlueRfqServer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge 5 | { 6 | public interface IGlueRfqServer 7 | { 8 | void Start(IGlueRfqBridge bridge); 9 | void Stop(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqResponseType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge.Models 5 | { 6 | public enum RfqResponseType 7 | { 8 | SetRequestId, 9 | Quote, 10 | Expired, 11 | LostConnection, 12 | Error 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /StartBot.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | set GLUE-ENV=LOCAL 4 | set GLUE-REGION=TICK42 5 | set T42_DIR=%CD% 6 | 7 | SET prog="!CD!\GlueSymphonyRfqBridge\bin\Release\GlueSymphonyRfqBridge.exe" 8 | SET cert="!CD!\GlueSymphonyRfqBridge\bin\Release\Config\nws.gluerfq-cert.p12" 9 | SET certPwd="changeit" 10 | START "RFQ Bridge" !prog! !cert! !certPwd! 11 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Event.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | namespace GlueSymphonyRfqBridge 6 | { 7 | public class Event : EventArgs where T : class 8 | { 9 | public Event(T data) 10 | { 11 | Data = data; 12 | } 13 | 14 | public T Data { get; private set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge.Models 5 | { 6 | public enum RfqCommand 7 | { 8 | Help, 9 | Error, 10 | Request, 11 | Response, 12 | Expired, 13 | RequestToCounterPartyComment, 14 | CounterToRequestPartyComment 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Glue/GlueRfqBridgeConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using DOT.AGM.Client; 5 | 6 | namespace GlueSymphonyRfqBridge.Glue 7 | { 8 | public class GlueRfqBridgeConfiguration 9 | { 10 | public GlueRfqBridgeConfiguration( 11 | IClient client) 12 | { 13 | Client = client; 14 | } 15 | 16 | public IClient Client { get; private set; } 17 | } 18 | } -------------------------------------------------------------------------------- /JavaScriptApps/client/README.md: -------------------------------------------------------------------------------- 1 | # rfq 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /JavaScriptApps/server/README.md: -------------------------------------------------------------------------------- 1 | # rfq 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqCommentSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge.Models 5 | { 6 | // Subscription to T42.RFQ.Comments 7 | public class RfqCommentSubscription 8 | { 9 | // the party which subscribes 10 | public string PartyName { get; private set; } 11 | 12 | public RfqCommentSubscription(string partyName) 13 | { 14 | PartyName = partyName; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/ProductDetails.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | 6 | namespace GlueSymphonyRfqBridge.Models 7 | { 8 | // Not used at the moment 9 | public class ProductDetails 10 | { 11 | public string AssetClass { get; set; } 12 | public string UnderlyingName { get; set; } 13 | public DateTime? Expiry { get; set; } 14 | 15 | public override string ToString() 16 | { 17 | return string.Format("[ProductDetails: AssetClass={0}, UnderlyingName={1}, Expiry={2}]", AssetClass, UnderlyingName, Expiry); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/NumericExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System.Globalization; 5 | 6 | namespace GlueSymphonyRfqBridge 7 | { 8 | namespace Extensions 9 | { 10 | public static class NumericExtensions 11 | { 12 | public static double ParseDouble(this string text) 13 | { 14 | return double.Parse( 15 | text, 16 | NumberStyles.AllowThousands | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, 17 | CultureInfo.InvariantCulture); 18 | } 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /JavaScriptApps/client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | 'rules': { 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /JavaScriptApps/server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | 'rules': { 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 0 : 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/BotCommandExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System.Text.RegularExpressions; 5 | 6 | namespace GlueSymphonyRfqBridge.Models 7 | { 8 | namespace Extensions 9 | { 10 | public static class BotCommandExtensions 11 | { 12 | public static BotCommand WithRegexGroup( 13 | this BotCommand command, 14 | GroupCollection group, 15 | params string[] parameterNames) 16 | { 17 | foreach (var parameter in parameterNames) 18 | { 19 | command = command.Add(parameter, group[parameter].Value); 20 | } 21 | return command; 22 | } 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Config/overrides.LOCAL-TICK42.properties: -------------------------------------------------------------------------------- 1 | hostMachine=localhost 2 | tcpPort=22031 3 | rootSubjectSuffix=LOCAL 4 | 5 | # AGM 6 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport.tcp.uri=tcp://${hostMachine}:${tcpPort} 7 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.rootSubject=TICK42.AGM.${rootSubjectSuffix} 8 | agm.transportRepository.hub.hubTransports.RTT.parameters.serviceName=TICK42.AGM.${rootSubjectSuffix}.${T42_AGM_UserName} 9 | 10 | # Metrics 11 | metric.transport.RTT_TCP.driver.transport.tcp.uri=tcp://${hostMachine}:${tcpPort} 12 | metric.transport.RTT_TCP.driver.rootSubject=TICK42.METRICS.${rootSubjectSuffix} 13 | 14 | #Local TCP Server 15 | agm.server.lifetimeManager.plugins.tcpServer.lifetime=instance 16 | agm.server.lifetimeManager.plugins.tcpServer.typePath=DOT.AGM.dll, DOT.AGM.DOTTransport.TcpServerLifetimePlugin 17 | agm.server.lifetimeManager.plugins.tcpServer.enabled=true 18 | agm.server.lifetimeManager.plugins.tcpServer.port=${tcpPort} 19 | -------------------------------------------------------------------------------- /JavaScriptApps/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rfq 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /JavaScriptApps/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rfq 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('shelljs/global') 3 | env.NODE_ENV = 'production' 4 | 5 | var path = require('path') 6 | var config = require('../config') 7 | var ora = require('ora') 8 | var webpack = require('webpack') 9 | var webpackConfig = require('./webpack.prod.conf') 10 | 11 | console.log( 12 | ' Tip:\n' + 13 | ' Built files are meant to be served over an HTTP server.\n' + 14 | ' Opening index.html over file:// won\'t work.\n' 15 | ) 16 | 17 | var spinner = ora('building for production...') 18 | spinner.start() 19 | 20 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 21 | rm('-rf', assetsPath) 22 | mkdir('-p', assetsPath) 23 | cp('-R', 'static/*', assetsPath) 24 | 25 | webpack(webpackConfig, function (err, stats) { 26 | spinner.stop() 27 | if (err) throw err 28 | process.stdout.write(stats.toString({ 29 | colors: true, 30 | modules: false, 31 | children: false, 32 | chunks: false, 33 | chunkModules: false 34 | }) + '\n') 35 | }) 36 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('shelljs/global') 3 | env.NODE_ENV = 'production' 4 | 5 | var path = require('path') 6 | var config = require('../config') 7 | var ora = require('ora') 8 | var webpack = require('webpack') 9 | var webpackConfig = require('./webpack.prod.conf') 10 | 11 | console.log( 12 | ' Tip:\n' + 13 | ' Built files are meant to be served over an HTTP server.\n' + 14 | ' Opening index.html over file:// won\'t work.\n' 15 | ) 16 | 17 | var spinner = ora('building for production...') 18 | spinner.start() 19 | 20 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 21 | rm('-rf', assetsPath) 22 | mkdir('-p', assetsPath) 23 | cp('-R', 'static/*', assetsPath) 24 | 25 | webpack(webpackConfig, function (err, stats) { 26 | spinner.stop() 27 | if (err) throw err 28 | process.stdout.write(stats.toString({ 29 | colors: true, 30 | modules: false, 31 | children: false, 32 | chunks: false, 33 | chunkModules: false 34 | }) + '\n') 35 | }) 36 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge.userprefs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlueSymphonyRfqBridge", "GlueSymphonyRfqBridge\GlueSymphonyRfqBridge.csproj", "{1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqComment.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge.Models 5 | { 6 | public class RfqComment 7 | { 8 | public string RequestId { get; private set; } 9 | public string RequestParty { get; private set; } 10 | public string CounterParty { get; private set; } 11 | public string ProductName { get; private set; } 12 | public string Comment { get; private set; } 13 | 14 | public RfqComment( 15 | string requestId, 16 | string requestParty, 17 | string counterParty, 18 | string productName, 19 | string comment) 20 | { 21 | RequestId = requestId; 22 | RequestParty = requestParty; 23 | CounterParty = counterParty; 24 | ProductName = productName; 25 | Comment = comment; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | return string.Format("[RfqComment: RequestId={0}, RequestParty={1}, CounterParty={2}, ProductName={3}, Comment={4}]", RequestId, RequestParty, CounterParty, ProductName, Comment); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /JavaScriptApps/client/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '.', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'] 18 | }, 19 | dev: { 20 | env: require('./dev.env'), 21 | port: 8080, 22 | assetsSubDirectory: 'static', 23 | assetsPublicPath: '/', 24 | proxyTable: {}, 25 | // CSS Sourcemaps off by default because relative paths are "buggy" 26 | // with this option, according to the CSS-Loader README 27 | // (https://github.com/webpack/css-loader#sourcemaps) 28 | // In our experience, they generally work as expected, 29 | // just be aware of this issue when enabling this option. 30 | cssSourceMap: false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JavaScriptApps/server/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: './', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'] 18 | }, 19 | dev: { 20 | env: require('./dev.env'), 21 | port: 8080, 22 | assetsSubDirectory: 'static', 23 | assetsPublicPath: '/', 24 | proxyTable: {}, 25 | // CSS Sourcemaps off by default because relative paths are "buggy" 26 | // with this option, according to the CSS-Loader README 27 | // (https://github.com/webpack/css-loader#sourcemaps) 28 | // In our experience, they generally work as expected, 29 | // just be aware of this issue when enabling this option. 30 | cssSourceMap: false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | 8 | // add hot-reload related code to entry chunks 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': config.dev.env 22 | }), 23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 24 | new webpack.optimize.OccurenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }) 33 | ] 34 | }) 35 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | 8 | // add hot-reload related code to entry chunks 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': config.dev.env 22 | }), 23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 24 | new webpack.optimize.OccurenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }) 33 | ] 34 | }) 35 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Glue/GlueExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using DOT.AGM; 7 | 8 | namespace GlueSymphonyRfqBridge.Glue 9 | { 10 | namespace Extensions 11 | { 12 | public static class GlueContextValueExtensions 13 | { 14 | public static string TryGetString(this Dictionary args, string key) 15 | { 16 | IContextValue cv; 17 | if (!args.TryGetValue(key, out cv)) 18 | { 19 | return null; 20 | } 21 | return cv.Value.AsString; 22 | } 23 | 24 | public static double SafeGetDouble(this Dictionary args, string key) 25 | { 26 | var value = args[key].Value; 27 | switch (value.Type) 28 | { 29 | case AgmValueType.Int: 30 | return value.AsInt; 31 | case AgmValueType.Long: 32 | return value.AsLong; 33 | case AgmValueType.Double: 34 | return value.AsDouble; 35 | default: 36 | throw new InvalidOperationException("Cannot get " + key + " as double from " + value); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/BotCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace GlueSymphonyRfqBridge.Models 8 | { 9 | public class BotCommand 10 | { 11 | public Dictionary Arguments { get; private set; } 12 | 13 | public BotCommand(RfqCommand command) 14 | { 15 | Command = command; 16 | Arguments = new Dictionary(); 17 | } 18 | 19 | public string this[string parameter] 20 | { 21 | get 22 | { 23 | var result = Get(parameter); 24 | if (string.IsNullOrWhiteSpace(result)) 25 | { 26 | throw new ArgumentException( 27 | "Required parameter " + parameter + " not present in message"); 28 | } 29 | return result; 30 | } 31 | } 32 | 33 | public string Get(string parameter) 34 | { 35 | string result; 36 | Arguments.TryGetValue(parameter, out result); 37 | return result; 38 | } 39 | 40 | public RfqCommand Command { get; private set; } 41 | 42 | public BotCommand Add(string parameter, string value) 43 | { 44 | Arguments[parameter] = value; 45 | return this; 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Symphony/SymphonyRfqBridgeConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | 6 | namespace GlueSymphonyRfqBridge.Symphony 7 | { 8 | public class SymphonyRfqBridgeConfiguration 9 | { 10 | public SymphonyRfqBridgeConfiguration( 11 | string botCertificateFilePath, 12 | string botCertificatePassword) 13 | { 14 | BotCertificateFilePath = botCertificateFilePath; 15 | BotCertificatePassword = botCertificatePassword; 16 | BaseApiUrl = "https://foundation-dev-api.symphony.com"; 17 | BasePodUrl = "https://foundation-dev.symphony.com"; 18 | TimeoutInMillis = 35000; // because https://github.com/symphonyoss/RestApiClient/issues/22 19 | DefaultRfqExpiry = TimeSpan.FromMinutes(15); 20 | } 21 | 22 | public string BotCertificateFilePath { get; private set; } 23 | public string BotCertificatePassword { get; private set; } 24 | 25 | public string BaseApiUrl { get; set; } 26 | public string BasePodUrl { get; set; } 27 | 28 | public int TimeoutInMillis { get; set; } 29 | public TimeSpan DefaultRfqExpiry { get; set; } 30 | 31 | public override string ToString() 32 | { 33 | return string.Format("[SymphonyRfqBridgeConfiguration: BotCertificateFilePath={0}, BotCertificatePassword={1}, BaseApiUrl={2}, BasePodUrl={3}, TimeoutInMillis={4}]", BotCertificateFilePath, BotCertificatePassword, BaseApiUrl, BasePodUrl, TimeoutInMillis); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | [assembly: AssemblyTitle("Tick42 GLUE/Symphony RFQ Bridge")] 12 | [assembly: AssemblyDescription("")] 13 | [assembly: AssemblyConfiguration("")] 14 | [assembly: AssemblyCompany("")] 15 | [assembly: AssemblyProduct("GlueSymphonyRfqBridge")] 16 | [assembly: AssemblyCopyright("Copyright © Tick42 2016")] 17 | [assembly: AssemblyTrademark("")] 18 | [assembly: AssemblyCulture("")] 19 | 20 | // Setting ComVisible to false makes the types in this assembly not visible 21 | // to COM components. If you need to access a type in this assembly from 22 | // COM, set the ComVisible attribute to true on that type. 23 | [assembly: ComVisible(false)] 24 | 25 | // The following GUID is for the ID of the typelib if this project is exposed to COM 26 | [assembly: Guid("1d81e5ae-7b54-4f1c-b4dc-33862d03b7bd")] 27 | 28 | // Version information for an assembly consists of the following four values: 29 | // 30 | // Major Version 31 | // Minor Version 32 | // Build Number 33 | // Revision 34 | // 35 | // You can specify all the values or you can default the Build and Revision Numbers 36 | // by using the '*' as shown below: 37 | // [assembly: AssemblyVersion("1.0.*")] 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] 40 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqQuoteRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | 6 | namespace GlueSymphonyRfqBridge.Models 7 | { 8 | public class RfqQuoteRequest 9 | { 10 | public string RequestId { get; private set; } 11 | public string RequestParty { get; private set; } 12 | public string CounterParty { get; private set; } 13 | public string ProductName { get; private set; } 14 | public ProductDetails ProductDetails { get; private set; } 15 | public double Quantity { get; private set; } 16 | public DateTime RequestExpirationDate { get; private set; } 17 | 18 | public RfqQuoteRequest( 19 | string requestId, 20 | string requestParty, 21 | string counterParty, 22 | string productName, 23 | ProductDetails productDetails, 24 | double quantity, 25 | DateTime requestExpirationDate) 26 | { 27 | RequestId = requestId; 28 | RequestParty = requestParty; 29 | CounterParty = counterParty; 30 | ProductName = productName; 31 | ProductDetails = productDetails; 32 | RequestExpirationDate = requestExpirationDate; 33 | Quantity = quantity; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return string.Format("[RfqQuoteRequest: RequestId={0}, RequestParty={1}, CounterParty={2}, ProductName={3}, ProductDetails={4}, Quantity={5}, RequestExpirationDate={6}]", RequestId, RequestParty, CounterParty, ProductName, ProductDetails, Quantity, RequestExpirationDate); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqQuoteInquiry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | 6 | namespace GlueSymphonyRfqBridge.Models 7 | { 8 | public class RfqQuoteInquiry 9 | { 10 | public string RequestId { get; private set; } 11 | public string RequestParty { get; private set; } 12 | public string ProductName { get; private set; } 13 | public ProductDetails ProductDetails { get; private set; } 14 | public double Quantity { get; private set; } 15 | public DateTime RequestExpirationDate { get; private set; } 16 | public string[] CounterParties { get; private set; } 17 | 18 | public RfqQuoteInquiry( 19 | string requestId, 20 | string requestParty, 21 | string productName, 22 | ProductDetails productDetails, 23 | double quantity, 24 | DateTime requestExpirationDate, 25 | params string[] counterParties) 26 | { 27 | RequestId = requestId; 28 | CounterParties = counterParties; 29 | RequestExpirationDate = requestExpirationDate; 30 | Quantity = quantity; 31 | ProductDetails = productDetails; 32 | ProductName = productName; 33 | RequestParty = requestParty; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return string.Format("[RfqQuoteInquiry: RequestId={0}, RequestParty={1}, ProductName={2}, ProductDetails={3}, Quantity={4}, RequestExpirationDate={5}, CounterParties={6}]", RequestId, RequestParty, ProductName, ProductDetails, Quantity, RequestExpirationDate, string.Join(", ", CounterParties)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JavaScriptApps/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfq", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Ivan Pidov ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "lint": "eslint --ext .js,.vue src" 11 | }, 12 | "dependencies": { 13 | "moment": "^2.15.1", 14 | "vue": "^2.0.1", 15 | "vue-toastr": "^2.0.4" 16 | }, 17 | "devDependencies": { 18 | "autoprefixer": "^6.4.0", 19 | "babel-core": "^6.0.0", 20 | "babel-eslint": "^7.0.0", 21 | "babel-loader": "^6.0.0", 22 | "babel-plugin-transform-runtime": "^6.0.0", 23 | "babel-preset-es2015": "^6.0.0", 24 | "babel-preset-stage-2": "^6.0.0", 25 | "babel-register": "^6.0.0", 26 | "connect-history-api-fallback": "^1.1.0", 27 | "css-loader": "^0.25.0", 28 | "eslint": "^3.6.1", 29 | "eslint-friendly-formatter": "^2.0.5", 30 | "eslint-loader": "^1.5.0", 31 | "eslint-plugin-html": "^1.3.0", 32 | "eslint-config-standard": "^6.1.0", 33 | "eslint-plugin-promise": "^2.0.1", 34 | "eslint-plugin-standard": "^2.0.1", 35 | "eventsource-polyfill": "^0.9.6", 36 | "express": "^4.13.3", 37 | "extract-text-webpack-plugin": "^1.0.1", 38 | "file-loader": "^0.9.0", 39 | "function-bind": "^1.0.2", 40 | "html-webpack-plugin": "^2.8.1", 41 | "http-proxy-middleware": "^0.17.2", 42 | "json-loader": "^0.5.4", 43 | "opn": "^4.0.2", 44 | "ora": "^0.3.0", 45 | "shelljs": "^0.7.4", 46 | "url-loader": "^0.5.7", 47 | "vue-loader": "^9.4.0", 48 | "vue-style-loader": "^1.0.0", 49 | "webpack": "^1.13.2", 50 | "webpack-dev-middleware": "^1.8.3", 51 | "webpack-hot-middleware": "^2.12.2", 52 | "webpack-merge": "^0.14.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites: 2 | 3 | To build the code you'll need Tick42 DLLs (namely DOT.AGM.dll and its dependencies). To obtain those DLLs, please contact us at . 4 | 5 | # How to deploy GlueSymphonyRfqBridge 6 | 7 | ## 1. Contents: 8 | 9 | This package includes 3 components: 10 | 11 | - GlueSymphonyRfqBridge 12 | - CounterRFQ.xlsm and RequestRFQ.xlsm 13 | - Javascript web apps 14 | 15 | ## 2. Dependencies not included in this repo: 16 | - GlueXL (so that xlsm files can talk to one another) 17 | - AGM Bridge (if you want to use the web apps) 18 | - HTML Server (to host the JS apps) 19 | - Tick42 libs referenced by GlueSymphonyRfqBridge (DOT.AGM and its dependencies) 20 | - PricePublisher (to populate the JS Server web app with mock market data, so it can autoreply on RFQ inquiries) 21 | 22 | ## 3. How to build the code: 23 | Get in touch with Tick42 so that you can get an installer for the Tick42 DLL dependencies referenced by the RFQ Bridge. The installer also contains the support applications required by the JS web apps. 24 | 25 | ## 4. How to run the demo 26 | - Start the bridge using StartBot.cmd (assuming you've built it in Release mode) or from VisualStudio. 27 | - At this point you should be able to use the two XLSM files to post RFQ queries from one to the other as well as chat messages. Integration to Symphony should also be available at this point. 28 | - If you want to use the web apps, you'd have to get a copy of the ServerBridge. It's included in the installer for the DLL dependencies. 29 | - Host the JS apps using a HTTP server. Built versions of the apps, a http-server and Node are included in the installer (JavaScript) 30 | - At this point you should be able to send chat messages from the apps to Excel/Symphony and vice versa. 31 | - If you want to use the autorespond JS server feature, you'd have to start the PricePublisher. 32 | -------------------------------------------------------------------------------- /JavaScriptApps/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfq", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Ivan Pidov ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "lint": "eslint --ext .js,.vue src" 11 | }, 12 | "dependencies": { 13 | "moment": "^2.15.1", 14 | "vue": "^2.0.1", 15 | "vue-input-tag": "0.0.9", 16 | "vue-toastr": "^2.0.4" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^6.4.0", 20 | "babel-core": "^6.0.0", 21 | "babel-eslint": "^7.0.0", 22 | "babel-loader": "^6.0.0", 23 | "babel-plugin-transform-runtime": "^6.0.0", 24 | "babel-preset-es2015": "^6.0.0", 25 | "babel-preset-stage-2": "^6.0.0", 26 | "babel-register": "^6.0.0", 27 | "connect-history-api-fallback": "^1.1.0", 28 | "css-loader": "^0.25.0", 29 | "eslint": "^3.6.1", 30 | "eslint-friendly-formatter": "^2.0.5", 31 | "eslint-loader": "^1.5.0", 32 | "eslint-plugin-html": "^1.3.0", 33 | "eslint-config-standard": "^6.1.0", 34 | "eslint-plugin-promise": "^2.0.1", 35 | "eslint-plugin-standard": "^2.0.1", 36 | "eventsource-polyfill": "^0.9.6", 37 | "express": "^4.13.3", 38 | "extract-text-webpack-plugin": "^1.0.1", 39 | "file-loader": "^0.9.0", 40 | "function-bind": "^1.0.2", 41 | "html-webpack-plugin": "^2.8.1", 42 | "http-proxy-middleware": "^0.17.2", 43 | "json-loader": "^0.5.4", 44 | "opn": "^4.0.2", 45 | "ora": "^0.3.0", 46 | "shelljs": "^0.7.4", 47 | "url-loader": "^0.5.7", 48 | "vue-loader": "^9.4.0", 49 | "vue-style-loader": "^1.0.0", 50 | "webpack": "^1.13.2", 51 | "webpack-dev-middleware": "^1.8.3", 52 | "webpack-hot-middleware": "^2.12.2", 53 | "webpack-merge": "^0.14.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Models/RfqQuoteInquiryResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | namespace GlueSymphonyRfqBridge.Models 5 | { 6 | public class RfqQuoteInquiryResponse 7 | { 8 | public RfqResponseType ResponseType { get; private set; } 9 | public string ResponseMessage { get; private set; } 10 | public string RequestId { get; private set; } 11 | public string RequestParty { get; private set; } 12 | public string CounterParty { get; private set; } 13 | public string ProductName { get; private set; } 14 | public ProductDetails ProductDetails { get; private set; } 15 | public double? Quantity { get; private set; } 16 | public double? Price { get; private set; } 17 | 18 | public RfqQuoteInquiryResponse( 19 | RfqResponseType responseType, 20 | string responseMessage, 21 | string requestId, 22 | string requestParty, 23 | string counterParty, 24 | string productName, 25 | ProductDetails productDetails, 26 | double? quantity, 27 | double? price) 28 | { 29 | RequestParty = requestParty; 30 | ResponseMessage = responseMessage; 31 | Price = price; 32 | Quantity = quantity; 33 | ProductDetails = productDetails; 34 | ProductName = productName; 35 | CounterParty = counterParty; 36 | RequestId = requestId; 37 | ResponseType = responseType; 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return string.Format("[RfqQuoteInquiryResponse: ResponseType={0}, ResponseMessage={1}, RequestId={2}, CounterParty={3}, ProductName={4}, ProductDetails={5}, Quantity={6}, Price={7}]", ResponseType, ResponseMessage, RequestId, CounterParty, ProductName, ProductDetails, Quantity, Price); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | if (options.extract) { 29 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 30 | } else { 31 | return ['vue-style-loader', sourceLoader].join('!') 32 | } 33 | } 34 | 35 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 36 | return { 37 | css: generateLoaders(['css']), 38 | postcss: generateLoaders(['css']), 39 | less: generateLoaders(['css', 'less']), 40 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 41 | scss: generateLoaders(['css', 'sass']), 42 | stylus: generateLoaders(['css', 'stylus']), 43 | styl: generateLoaders(['css', 'stylus']) 44 | } 45 | } 46 | 47 | // Generate loaders for standalone style files (outside of .vue) 48 | exports.styleLoaders = function (options) { 49 | var output = [] 50 | var loaders = exports.cssLoaders(options) 51 | for (var extension in loaders) { 52 | var loader = loaders[extension] 53 | output.push({ 54 | test: new RegExp('\\.' + extension + '$'), 55 | loader: loader 56 | }) 57 | } 58 | return output 59 | } 60 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | if (options.extract) { 29 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 30 | } else { 31 | return ['vue-style-loader', sourceLoader].join('!') 32 | } 33 | } 34 | 35 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 36 | return { 37 | css: generateLoaders(['css']), 38 | postcss: generateLoaders(['css']), 39 | less: generateLoaders(['css', 'less']), 40 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 41 | scss: generateLoaders(['css', 'sass']), 42 | stylus: generateLoaders(['css', 'stylus']), 43 | styl: generateLoaders(['css', 'stylus']) 44 | } 45 | } 46 | 47 | // Generate loaders for standalone style files (outside of .vue) 48 | exports.styleLoaders = function (options) { 49 | var output = [] 50 | var loaders = exports.cssLoaders(options) 51 | for (var extension in loaders) { 52 | var loader = loaders[extension] 53 | output.push({ 54 | test: new RegExp('\\.' + extension + '$'), 55 | loader: loader 56 | }) 57 | } 58 | return output 59 | } 60 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var opn = require('opn') 6 | var proxyMiddleware = require('http-proxy-middleware') 7 | var webpackConfig = require('./webpack.dev.conf') 8 | 9 | // default port where dev server listens for incoming traffic 10 | var port = process.env.PORT || config.dev.port 11 | // Define HTTP proxies to your custom API backend 12 | // https://github.com/chimurai/http-proxy-middleware 13 | var proxyTable = config.dev.proxyTable 14 | 15 | var app = express() 16 | var compiler = webpack(webpackConfig) 17 | 18 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 19 | publicPath: webpackConfig.output.publicPath, 20 | stats: { 21 | colors: true, 22 | chunks: false 23 | } 24 | }) 25 | 26 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 27 | // force page reload when html-webpack-plugin template changes 28 | compiler.plugin('compilation', function (compilation) { 29 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 30 | hotMiddleware.publish({ action: 'reload' }) 31 | cb() 32 | }) 33 | }) 34 | 35 | // proxy api requests 36 | Object.keys(proxyTable).forEach(function (context) { 37 | var options = proxyTable[context] 38 | if (typeof options === 'string') { 39 | options = { target: options } 40 | } 41 | app.use(proxyMiddleware(context, options)) 42 | }) 43 | 44 | // handle fallback for HTML5 history API 45 | app.use(require('connect-history-api-fallback')()) 46 | 47 | // serve webpack bundle output 48 | app.use(devMiddleware) 49 | 50 | // enable hot-reload and state-preserving 51 | // compilation error display 52 | app.use(hotMiddleware) 53 | 54 | // serve pure static assets 55 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 56 | app.use(staticPath, express.static('./static')) 57 | 58 | module.exports = app.listen(port, function (err) { 59 | if (err) { 60 | console.log(err) 61 | return 62 | } 63 | var uri = 'http://localhost:' + port 64 | console.log('Listening at ' + uri + '\n') 65 | opn(uri) 66 | }) 67 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var opn = require('opn') 6 | var proxyMiddleware = require('http-proxy-middleware') 7 | var webpackConfig = require('./webpack.dev.conf') 8 | 9 | // default port where dev server listens for incoming traffic 10 | var port = process.env.PORT || config.dev.port 11 | // Define HTTP proxies to your custom API backend 12 | // https://github.com/chimurai/http-proxy-middleware 13 | var proxyTable = config.dev.proxyTable 14 | 15 | var app = express() 16 | var compiler = webpack(webpackConfig) 17 | 18 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 19 | publicPath: webpackConfig.output.publicPath, 20 | stats: { 21 | colors: true, 22 | chunks: false 23 | } 24 | }) 25 | 26 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 27 | // force page reload when html-webpack-plugin template changes 28 | compiler.plugin('compilation', function (compilation) { 29 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 30 | hotMiddleware.publish({ action: 'reload' }) 31 | cb() 32 | }) 33 | }) 34 | 35 | // proxy api requests 36 | Object.keys(proxyTable).forEach(function (context) { 37 | var options = proxyTable[context] 38 | if (typeof options === 'string') { 39 | options = { target: options } 40 | } 41 | app.use(proxyMiddleware(context, options)) 42 | }) 43 | 44 | // handle fallback for HTML5 history API 45 | app.use(require('connect-history-api-fallback')()) 46 | 47 | // serve webpack bundle output 48 | app.use(devMiddleware) 49 | 50 | // enable hot-reload and state-preserving 51 | // compilation error display 52 | app.use(hotMiddleware) 53 | 54 | // serve pure static assets 55 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 56 | app.use(staticPath, express.static('./static')) 57 | 58 | module.exports = app.listen(port, function (err) { 59 | if (err) { 60 | console.log(err) 61 | return 62 | } 63 | var uri = 'http://localhost:' + port 64 | console.log('Listening at ' + uri + '\n') 65 | opn(uri) 66 | }) 67 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var projectRoot = path.resolve(__dirname, '../') 5 | 6 | module.exports = { 7 | entry: { 8 | app: './src/main.js' 9 | }, 10 | output: { 11 | path: config.build.assetsRoot, 12 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 13 | filename: '[name].js' 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.vue'], 17 | fallback: [path.join(__dirname, '../node_modules')], 18 | alias: { 19 | 'vue': 'vue/dist/vue', 20 | 'src': path.resolve(__dirname, '../src'), 21 | 'assets': path.resolve(__dirname, '../src/assets'), 22 | 'components': path.resolve(__dirname, '../src/components') 23 | } 24 | }, 25 | resolveLoader: { 26 | fallback: [path.join(__dirname, '../node_modules')] 27 | }, 28 | module: { 29 | preLoaders: [ 30 | { 31 | test: /\.vue$/, 32 | loader: 'eslint', 33 | include: projectRoot, 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'eslint', 39 | include: projectRoot, 40 | exclude: /node_modules/ 41 | } 42 | ], 43 | loaders: [ 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue' 47 | }, 48 | { 49 | test: /\.js$/, 50 | loader: 'babel', 51 | include: projectRoot, 52 | exclude: /node_modules/ 53 | }, 54 | { 55 | test: /\.json$/, 56 | loader: 'json' 57 | }, 58 | { 59 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 60 | loader: 'url', 61 | query: { 62 | limit: 10000, 63 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 64 | } 65 | }, 66 | { 67 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 68 | loader: 'url', 69 | query: { 70 | limit: 10000, 71 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 72 | } 73 | } 74 | ] 75 | }, 76 | eslint: { 77 | formatter: require('eslint-friendly-formatter') 78 | }, 79 | vue: { 80 | loaders: utils.cssLoaders(), 81 | postcss: [ 82 | require('autoprefixer')({ 83 | browsers: ['last 2 versions'] 84 | }) 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var projectRoot = path.resolve(__dirname, '../') 5 | 6 | module.exports = { 7 | entry: { 8 | app: './src/main.js' 9 | }, 10 | output: { 11 | path: config.build.assetsRoot, 12 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 13 | filename: '[name].js' 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.vue'], 17 | fallback: [path.join(__dirname, '../node_modules')], 18 | alias: { 19 | 'vue': 'vue/dist/vue', 20 | 'src': path.resolve(__dirname, '../src'), 21 | 'assets': path.resolve(__dirname, '../src/assets'), 22 | 'components': path.resolve(__dirname, '../src/components') 23 | } 24 | }, 25 | resolveLoader: { 26 | fallback: [path.join(__dirname, '../node_modules')] 27 | }, 28 | module: { 29 | preLoaders: [ 30 | { 31 | test: /\.vue$/, 32 | loader: 'eslint', 33 | include: projectRoot, 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'eslint', 39 | include: projectRoot, 40 | exclude: /node_modules/ 41 | } 42 | ], 43 | loaders: [ 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue' 47 | }, 48 | { 49 | test: /\.js$/, 50 | loader: 'babel', 51 | include: projectRoot, 52 | exclude: /node_modules/ 53 | }, 54 | { 55 | test: /\.json$/, 56 | loader: 'json' 57 | }, 58 | { 59 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 60 | loader: 'url', 61 | query: { 62 | limit: 10000, 63 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 64 | } 65 | }, 66 | { 67 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 68 | loader: 'url', 69 | query: { 70 | limit: 10000, 71 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 72 | } 73 | } 74 | ] 75 | }, 76 | eslint: { 77 | formatter: require('eslint-friendly-formatter') 78 | }, 79 | vue: { 80 | loaders: utils.cssLoaders(), 81 | postcss: [ 82 | require('autoprefixer')({ 83 | browsers: ['last 2 versions'] 84 | }) 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /JavaScriptApps/client/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var env = config.build.env 10 | 11 | var webpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 14 | }, 15 | devtool: config.build.productionSourceMap ? '#source-map' : false, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 19 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 20 | }, 21 | vue: { 22 | loaders: utils.cssLoaders({ 23 | sourceMap: config.build.productionSourceMap, 24 | extract: true 25 | }) 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env 31 | }), 32 | new webpack.optimize.OccurenceOrderPlugin(), 33 | // extract css into its own file 34 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 35 | // generate dist index.html with correct asset hash for caching. 36 | // you can customize output by editing /index.html 37 | // see https://github.com/ampedandwired/html-webpack-plugin 38 | new HtmlWebpackPlugin({ 39 | filename: config.build.index, 40 | template: 'index.html', 41 | inject: true, 42 | minify: { 43 | removeComments: true, 44 | collapseWhitespace: true, 45 | removeAttributeQuotes: true 46 | // more options: 47 | // https://github.com/kangax/html-minifier#options-quick-reference 48 | }, 49 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 50 | chunksSortMode: 'dependency' 51 | }), 52 | // split vendor js into its own file 53 | new webpack.optimize.CommonsChunkPlugin({ 54 | name: 'vendor', 55 | minChunks: function (module, count) { 56 | // any required modules inside node_modules are extracted to vendor 57 | return ( 58 | module.resource && 59 | /\.js$/.test(module.resource) && 60 | module.resource.indexOf( 61 | path.join(__dirname, '../node_modules') 62 | ) === 0 63 | ) 64 | } 65 | }), 66 | // extract webpack runtime and module manifest to its own file in order to 67 | // prevent vendor hash from being updated whenever app bundle is updated 68 | new webpack.optimize.CommonsChunkPlugin({ 69 | name: 'manifest', 70 | chunks: ['vendor'] 71 | }) 72 | ] 73 | }) 74 | 75 | if (config.build.productionGzip) { 76 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 77 | 78 | webpackConfig.plugins.push( 79 | new CompressionWebpackPlugin({ 80 | asset: '[path].gz[query]', 81 | algorithm: 'gzip', 82 | test: new RegExp( 83 | '\\.(' + 84 | config.build.productionGzipExtensions.join('|') + 85 | ')$' 86 | ), 87 | threshold: 10240, 88 | minRatio: 0.8 89 | }) 90 | ) 91 | } 92 | 93 | module.exports = webpackConfig 94 | -------------------------------------------------------------------------------- /JavaScriptApps/server/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var env = config.build.env 10 | 11 | var webpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 14 | }, 15 | devtool: config.build.productionSourceMap ? '#source-map' : false, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 19 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 20 | }, 21 | vue: { 22 | loaders: utils.cssLoaders({ 23 | sourceMap: config.build.productionSourceMap, 24 | extract: true 25 | }) 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env 31 | }), 32 | new webpack.optimize.OccurenceOrderPlugin(), 33 | // extract css into its own file 34 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 35 | // generate dist index.html with correct asset hash for caching. 36 | // you can customize output by editing /index.html 37 | // see https://github.com/ampedandwired/html-webpack-plugin 38 | new HtmlWebpackPlugin({ 39 | filename: config.build.index, 40 | template: 'index.html', 41 | inject: true, 42 | minify: { 43 | removeComments: true, 44 | collapseWhitespace: true, 45 | removeAttributeQuotes: true 46 | // more options: 47 | // https://github.com/kangax/html-minifier#options-quick-reference 48 | }, 49 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 50 | chunksSortMode: 'dependency' 51 | }), 52 | // split vendor js into its own file 53 | new webpack.optimize.CommonsChunkPlugin({ 54 | name: 'vendor', 55 | minChunks: function (module, count) { 56 | // any required modules inside node_modules are extracted to vendor 57 | return ( 58 | module.resource && 59 | /\.js$/.test(module.resource) && 60 | module.resource.indexOf( 61 | path.join(__dirname, '../node_modules') 62 | ) === 0 63 | ) 64 | } 65 | }), 66 | // extract webpack runtime and module manifest to its own file in order to 67 | // prevent vendor hash from being updated whenever app bundle is updated 68 | new webpack.optimize.CommonsChunkPlugin({ 69 | name: 'manifest', 70 | chunks: ['vendor'] 71 | }) 72 | ] 73 | }) 74 | 75 | if (config.build.productionGzip) { 76 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 77 | 78 | webpackConfig.plugins.push( 79 | new CompressionWebpackPlugin({ 80 | asset: '[path].gz[query]', 81 | algorithm: 'gzip', 82 | test: new RegExp( 83 | '\\.(' + 84 | config.build.productionGzipExtensions.join('|') + 85 | ')$' 86 | ), 87 | threshold: 10240, 88 | minRatio: 0.8 89 | }) 90 | ) 91 | } 92 | 93 | module.exports = webpackConfig 94 | -------------------------------------------------------------------------------- /RfqClient.md: -------------------------------------------------------------------------------- 1 | #### Triggering an RFQ 2 | 3 | RFQ (request for quote) is triggered when the user presses the *"Send RFQ"* button. 4 | 5 | The client app should trigger an RFQ by subscribing on `T42.RFQ.QuoteInquiryStream`, passing: 6 | 7 | |Field|Description| 8 | |-----|-----------| 9 | |`string requestParty`|The e-mail of the requesting party| 10 | |`string[] counterParties`|The e-mails of the counter parties which the requesting party expects to receive quotes from| 11 | |`string productName`|The name of the product for which this RFQ is made, usually an OTC, non-tradeable instrument| 12 | |`double quantity`|The number of *units* of the product the requesting party wants to buy or sell. If `quantity` is `> 0` that's a *BUY*, otherwise a *SELL* RFQ request| 13 | |`DateTime requestExpirationDate`|The absolute time when this RFQ expires| 14 | 15 | #### Receiving Quotes 16 | 17 | At some point, the app will start receiving quotes from one or more counter parties on the *same* `T42.RFQ.QuoteInquiryStream` stream. The stream data has the following shape: 18 | 19 | |Field|Description| 20 | |-----|-----------| 21 | |`string responseType`|One of: `SetRequestId` (`requestId` only), `Quote` (almost all fields), `Expired` (`responseMessage` only), `Error` (`responseMessage` only)| 22 | |`string requestId`|The RFQ request's generated ID| 23 | |`string? responseMessage`|Usually the error message if `Error`| 24 | |`string? counterParty`|The party sending the RFQ response. Initially, the RFQ bridge will stream `SetRequestId` with `requestId` set to an auto-generated value from the bridge. 25 | |`string? productName`|The product in the RFQ (this will never be filled in in phase #1)| 26 | |`double? quantity`|The quantity that's requested. A quote response can specify a different (lesser or bigger) value. If no value is passed, it's the same quantity as in the RFQ| 27 | |`double? price`|The price quoted from the counter party| 28 | 29 | #### Receiving from, and sending comments to counter party 30 | 31 | The client app can subscribe for counter party comments on `T42.RFQ.CounterPartyCommentStream` passing its request party name. 32 | 33 | Once subscribed, the request party can send a comment by calling `T42.RFQ.SendCommentToCounterParty` passing: 34 | 35 | |Field|Description| 36 | |-----|-----------| 37 | |`requestParty`|Email of request party| 38 | |`counterParty`|Target counter party's email| 39 | |*`requestId`*|Optional, the request Id of an RFQ for which this comment is about, otherwise the comment is not tied to an RFQ| 40 | |`comment`|Free-text message| 41 | 42 | The counter party can reply at any time, again either to an RFQ-related or general comment. The reply will be published on `T42.RFQ.CounterPartyCommentStream`, and the data will have the same shape as the request fields in `T42.RFQ.SendCommentToCounterParty` (so optional `requestId`, and then `requestParty`, `counterParty`, `comment`). 43 | 44 | ### Appendix - GLUE Method Signatures 45 | 46 | *Note:* All methods start with `T42.RFQ.` 47 | 48 | |Method|Steam?|Accepts|Returns| 49 | |------|------|-------|-------| 50 | |`CounterPartyCommentStream`|Y|`string requestParty`|`string requestParty, string? requestId, string counterParty, comment`| 51 | |`QuoteInquiryStream`|Y|`string requestParty, string[] counterParties, string productName, double quantity, DateTime requestExpirationDate`|`string requestId, string responseType, string? counterParty, string? productName, double? quantity, double? price`| 52 | |`SendCommentToCounterParty`|N|`string requestParty, string? requestId, string counterParty, string comment`|| 53 | 54 | 55 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Glue/GlueStreamHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using DOT.AGM.Server; 6 | 7 | namespace GlueSymphonyRfqBridge.Glue 8 | { 9 | public delegate bool AcceptSubscriptionDelegate( 10 | IEventStreamSubscriptionRequest subscriptionRequest, 11 | ref string message); 12 | 13 | public class GlueStreamHandler : IServerEventStreamHandler 14 | { 15 | private readonly AcceptSubscriptionDelegate acceptor_; 16 | private readonly Action subscriberAdded_; 17 | private readonly Action subscriberRemoved_; 18 | 19 | public GlueStreamHandler( 20 | AcceptSubscriptionDelegate acceptor, 21 | Action subscriberAdded = null, 22 | Action subscriberRemoved = null) 23 | { 24 | acceptor_ = acceptor; 25 | subscriberAdded_ = subscriberAdded; 26 | subscriberRemoved_ = subscriberRemoved; 27 | } 28 | 29 | public void HandleStreamBranchClosed( 30 | IServerEventStream serverEventStream, 31 | IEventStreamBranch branch, 32 | object streamCookie = null) 33 | { 34 | } 35 | 36 | public void HandleStreamClosed( 37 | IServerEventStream serverEventStream, 38 | object streamCookie = null) 39 | { 40 | } 41 | 42 | public void HandleSubscriber( 43 | IServerEventStream serverEventStream, 44 | IEventStreamSubscriber subscriber, 45 | IEventStreamBranch branch, 46 | object streamCookie = null) 47 | { 48 | if (subscriberAdded_ != null) 49 | { 50 | subscriberAdded_(subscriber); 51 | } 52 | } 53 | 54 | public void HandleSubscriberRemoved( 55 | IServerEventStream serverEventStream, 56 | IEventStreamSubscriber subscriber, 57 | EventStreamSubscriberRemovedContext subscriberRemovedContext, 58 | IEventStreamBranch branch = null, 59 | object streamCookie = null) 60 | { 61 | if (subscriberRemoved_ != null) 62 | { 63 | subscriberRemoved_(subscriber); 64 | } 65 | } 66 | 67 | public IEventStreamBranch HandleSubscriptionRequest( 68 | IServerEventStream serverEventStream, 69 | IEventStreamSubscriptionRequest subscriptionRequest, 70 | object streamCookie = null) 71 | { 72 | string message = null; 73 | 74 | try 75 | { 76 | if (!acceptor_(subscriptionRequest, ref message)) 77 | { 78 | subscriptionRequest.Reject( 79 | reply => reply.SetIsFailed(true).SetMessage(message).Build()); 80 | return null; 81 | } 82 | return subscriptionRequest.Accept( 83 | reply => reply.SetMessage(message ?? "Subscribed").Build()); 84 | } 85 | catch (Exception e) 86 | { 87 | subscriptionRequest.Reject( 88 | reply => reply.SetIsFailed(true) 89 | .SetMessage(string.Format("Unexpected error: {0}", e.Message)) 90 | .Build()); 91 | return null; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/IGlueRfqBridge.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using GlueSymphonyRfqBridge.Models; 6 | 7 | namespace GlueSymphonyRfqBridge 8 | { 9 | public interface IGlueRfqBridge 10 | { 11 | string Name { get; } 12 | 13 | void Start(); 14 | void Stop(); 15 | 16 | //-- "LOCAL" side 17 | 18 | // LOCAL side GLUE requestor client subscribes on T42.RFQ.QuoteInquiryStream 19 | // Bridge sends inquiry over chat to REMOTE side 20 | // REMOTE side receives both on chat and on T42.RFQ.QuoteRequestStream 21 | void SendQuoteInquiry(RfqQuoteInquiry quoteInquiry); 22 | 23 | // REMOTE side sends quote response over chat 24 | // LOCAL side GLUE requestor receives response as stream push on T42.RFQ.QuoteInquiryStream 25 | event EventHandler> QuoteInquiryResponseReceived; 26 | 27 | // LOCAL side GLUE requestor client posts comment on T42.RFQ.SendCommentToCounterParty 28 | // Bridge sends comment over chat to REMOTE side 29 | void SendCommentToCounterParty(RfqComment comment); 30 | 31 | // REMOTE side sends comment over chat 32 | // LOCAL side GLUE requestor client receives comment as stream push on T42.RFQ.CounterPartyCommentStream 33 | event EventHandler> CounterPartyCommentReceived; 34 | 35 | //-- "REMOTE" side 36 | 37 | // LOCAL side sends quote request over chat to REMOTE side 38 | // REMOTE side counterparty GLUE client receives quote requests as stream push on T42.RFQ.QuoteRequestStream 39 | event EventHandler> QuoteRequestReceived; 40 | 41 | // REMOTE side gets called T42.RFQ.SendQuoteResponse 42 | // REMOTE side sends quote response over chat 43 | // LOCAL side GLUE requestor client receives response on T42.RFQ.QuoteInquiryStream 44 | void SendQuoteInquiryResponse(RfqQuoteInquiryResponse quoteInquiryResponse); 45 | 46 | // LOCAL party sends request party comment over chat to REMOTE side 47 | // REMOTE side counterparty receives request party comment as stream push on T42.RFQ.RequestPartyCommentStream 48 | event EventHandler> RequestPartyCommentReceived; 49 | 50 | // REMOTE side posts comment to request part by calling T42.RFQ.SendCommentToRequestParty 51 | // Bridge sends comment to REMOTE side request party over char 52 | void SendCommentToRequestParty(RfqComment comment); 53 | 54 | // Called when the REMOTE party gets a subscription on T42.RFQ.RequestPartyCommentStream 55 | // so it can tell the bridge to start dispatching messages sent to this party 56 | void SubscribeForRequestPartyComments(string counterParty); 57 | void UnsubscribeForRequestPartyComments(string counterParty); 58 | 59 | // Called when the LOCAL party gets a subscription on T42.RFQ.CounterPartyCommentStream 60 | // so it can tell the bridge to start dispatching messages sent to the rquest party 61 | void SubscribeForCounterPartyComments(string requestParty); 62 | void UnsubscribeForCounterPartyComments(string requestParty); 63 | 64 | // Called when the REMOTE party gets a subscription on T42.RFQ.QuoteRequestStream 65 | // so it can tell the bridge to start listening to messages from this party 66 | void SubscribeForQuoteRequests(string counterParty); 67 | void UnsubscribeForQuoteRequests(string counterParty); 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Config/agm.properties: -------------------------------------------------------------------------------- 1 | # This file configures the AGM for HC. 2 | # The AGM Server and Client are configured separately although they almost always use same transports. 3 | # 4 | # The config is driven off two environment variables GLUE-ENV and GLUE-REGION e.g.: 5 | # GLUE-ENV=DEV 6 | # GLUE-REGION=NA 7 | # They need to be set before HC resp. ACS is run. 8 | # 9 | # The config file will load a default config before these environment variables are used to select overrides 10 | # Therefore if HC/ACS is run without these env variables set it will work using the default/demo config. 11 | 12 | AGM-ENV=%GLUE-ENV?LOCAL% 13 | AGM-REGION=%GLUE-REGION?TICK42% 14 | hostMachine=localhost 15 | ### 16 | # 17 | # client configuration 18 | # 19 | agm.client.configuration.applicationName=GlueSymphonyRfqBridge 20 | agm.client.configuration.machineName= 21 | agm.client.configuration.serverExpirationFactor=2 22 | agm.client.configuration.methodDiscoveryMode=both 23 | agm.client.configuration.removeOrphanMethods=true 24 | agm.client.configuration.keepOriginalServer=false 25 | 26 | agm.client.configuration.environment=${AGM-ENV} 27 | agm.client.configuration.region=${AGM-REGION} 28 | agm.client.configuration.userName= 29 | 30 | ### 31 | # 32 | # server configuration 33 | # 34 | agm.server.configuration.applicationName=GlueSymphonyRfqBridge 35 | agm.server.configuration.machineName= 36 | agm.server.configuration.userName= 37 | agm.server.configuration.environment=${AGM-ENV} 38 | agm.server.configuration.region=${AGM-REGION} 39 | agm.server.configuration.serviceName= 40 | agm.server.support.enabled=false 41 | 42 | ### 43 | # 44 | # transport sections 45 | # 46 | agm.client.transportSection=agm.transportRepository 47 | agm.server.transportSection=agm.transportRepository 48 | 49 | ### 50 | # 51 | # transport names 52 | # 53 | agm.client.transport.name=hub 54 | agm.server.transport.name=hub 55 | 56 | ### 57 | # 58 | # transportRepository 59 | # 60 | agm.transportRepository.hub.factoryTypePath=DOT.AGM.dll, DOT.AGM.HubTransport.HubTransportFactory 61 | agm.transportRepository.hub.hubTransports=Inproc, RTT 62 | agm.transportRepository.hub.hubTransports.RTT.factoryTypePath=DOT.AGM.dll, DOT.AGM.RTTTransport.RTTTransportFactory 63 | agm.transportRepository.hub.hubTransports.RTT.parameters.serviceName=TICK42.AGM.${AGM-REGION}.${AGM-ENV}.${T42_AGM_UserName} 64 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.rootSubject=TICK42.AGM.${AGM-REGION}.${AGM-ENV} 65 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.compressData=false 66 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport=tcp 67 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport.tcp.type=TCP 68 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport.tcp.enabled=true 69 | # the value for the tcp.uri parameter below is likely to be overridden by including separate properties file 70 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport.tcp.uri=tcp://${hostMachine}:22001 71 | agm.transportRepository.hub.hubTransports.RTT.parameters.driver.transport.tcp.sslIgnoreCertificateNameMismatch=true 72 | agm.transportRepository.hub.hubTransports.RTT.parameters.announcementInterval=30 73 | # DEBUG-start 74 | agm.transportRepository.hub.hubTransports.RTT.parameters.expirationFactor=4 75 | # DEBUG-end 76 | agm.transportRepository.hub.hubTransports.Inproc.factoryTypePath=DOT.AGM.dll, InprocTransport.InprocTransportFactory 77 | 78 | # The following line imports a property file like overrides.DEV-NA.properties IF IT EXISTS. 79 | # This can override any of the values set above. 80 | # By default this file only sets the TCP server and port URI. 81 | 82 | #@import? overrides.%GLUE-ENV?LOCAL%-%GLUE-REGION?TICK42%.properties -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Threading; 6 | using DOT.Logging; 7 | using GlueSymphonyRfqBridge.Glue; 8 | using GlueSymphonyRfqBridge.Symphony; 9 | 10 | namespace GlueSymphonyRfqBridge 11 | { 12 | class Program 13 | { 14 | private static readonly ISmartLogger Logger = new SmartLogger(typeof(Program)); 15 | 16 | public static volatile bool ShuttingDown; 17 | 18 | static void Main(string[] args) 19 | { 20 | try 21 | { 22 | new Program().Run(args); 23 | } 24 | catch (Exception e) 25 | { 26 | Logger.Error(e); 27 | } 28 | } 29 | 30 | private void Run(string[] args) 31 | { 32 | IGlueRfqServer server = null; 33 | IGlueRfqBridge bridge = null; 34 | 35 | Glue.Glue glue = null; 36 | try 37 | { 38 | if (args.Length != 0 && args.Length != 2) 39 | { 40 | var platform = Environment.OSVersion.Platform; 41 | if (platform == PlatformID.MacOSX || platform == PlatformID.Unix) 42 | { 43 | Logger.Error("Usage: mono GlueSymphonyRfqBridge.exe [ ]"); 44 | } 45 | else 46 | { 47 | Logger.Error("Usage: GlueSymphonyRfqBridge [ ]"); 48 | } 49 | //return; 50 | } 51 | 52 | glue = new Glue.Glue(); 53 | glue.Start(); 54 | 55 | if (args.Length == 2) 56 | { 57 | // ./Config/nws.gluerfq-cert.p12 changeit 58 | var certFilePath = args[0]; 59 | var certPassword = args[1]; 60 | bridge = new SymphonyRfqBridge( 61 | new SymphonyRfqBridgeConfiguration(certFilePath, certPassword)); 62 | } 63 | else 64 | { 65 | bridge = new GlueRfqBridge(); 66 | } 67 | 68 | server = new GlueRfqServer(glue.Server); 69 | 70 | Logger.InfoFormat("Starting {0} bridge...", bridge.Name); 71 | bridge.Start(); 72 | 73 | server.Start(bridge); 74 | 75 | var stopEvent = new ManualResetEventSlim(false); 76 | Console.CancelKeyPress += (sender, e) => 77 | { 78 | e.Cancel = true; 79 | if (e.SpecialKey != ConsoleSpecialKey.ControlC) 80 | { 81 | return; 82 | } 83 | ShuttingDown = true; 84 | Logger.Info("Ctrl-C pressed, terminating..."); 85 | stopEvent.Set(); 86 | }; 87 | Logger.Info("Press Ctrl-C to quit..."); 88 | stopEvent.Wait(); 89 | } 90 | catch (Exception e) 91 | { 92 | Logger.Error(e); 93 | } 94 | finally 95 | { 96 | if (bridge != null) 97 | { 98 | Logger.InfoFormat("Stopping {0} bridge...", bridge.Name); 99 | bridge.Stop(); 100 | Logger.InfoFormat("Bridge {0} stopped.", bridge.Name); 101 | } 102 | 103 | if (server != null) 104 | { 105 | server.Stop(); 106 | } 107 | 108 | if (glue != null) 109 | { 110 | glue.Stop(); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /JavaScriptApps/client/src/components/Row.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 120 | 121 | 122 | 127 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Glue/Glue.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System.Reflection; 5 | using DOT.AGM; 6 | using DOT.AGM.Client; 7 | using DOT.AGM.Core; 8 | using DOT.AGM.Core.Client; 9 | using DOT.AGM.Core.Server; 10 | using DOT.AGM.DOTTransport; 11 | using DOT.AGM.Server; 12 | using DOT.Core; 13 | using DOT.Core.Configuration; 14 | using DOT.Core.EventDispatcher; 15 | using DOT.Core.Isolation.System.IO; 16 | using DOT.Core.Serialization.Properties; 17 | using DOT.Core.Util; 18 | using DOT.Logging; 19 | 20 | namespace GlueSymphonyRfqBridge.Glue 21 | { 22 | /// 23 | /// Configures and starts/stops AGM Server and Client. Reads configuration from 'Config\agm.properties' 24 | /// 25 | class Glue 26 | { 27 | private static readonly ISmartLogger Logger = new SmartLogger(typeof(Glue)); 28 | 29 | public IServer Server { get; private set; } 30 | public IClient Client { get; private set; } 31 | 32 | public void Start() 33 | { 34 | Logger.Info("Starting GLUE..."); 35 | 36 | ApplicationGuiMethods.Touch(); 37 | var propertiesContext = new PropertiesSerializationContext(CumulativeExpandVariables.Instance, 38 | new DefaultTypeResolver(GlobalTypeResolver.Instance), 39 | GlobalConfigurationFactoryLocator.Instance); 40 | 41 | var configPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 42 | var agmPropertiesFile = Path.Combine(configPath, "Config", "agm.properties"); 43 | var agmProperties = GetProperties(agmPropertiesFile, propertiesContext); 44 | 45 | var transportFactory = new DOTTransportFactory(); 46 | var agmConfiguration = new AgmConfiguration(transportFactory); 47 | var agmFactory = new ApplicationGuiMethods(); 48 | agmFactory.Initialize(agmConfiguration); 49 | 50 | Client = SetupAgmClient(agmProperties, agmFactory, propertiesContext); 51 | Server = SetupAgmServer(agmProperties, agmFactory, propertiesContext); 52 | 53 | Logger.Info("GLUE started."); 54 | } 55 | 56 | public void Stop() 57 | { 58 | Logger.Info("Stopping GLUE..."); 59 | 60 | if (Server != null) 61 | { 62 | Server.Stop(); 63 | Server.Dispose(); 64 | } 65 | 66 | if (Client != null) 67 | { 68 | Client.Stop(); 69 | Client.Dispose(); 70 | } 71 | 72 | Logger.Info("GLUE stopped."); 73 | } 74 | 75 | private static IServer SetupAgmServer(Properties agmProperties, IApplicationGuiMethods agmFactory, PropertiesSerializationContext propertiesContext) 76 | { 77 | var serverConfiguration = (ServerConfiguration)ConfigurationHelper.LoadServerConfiguration(agmProperties, propertiesContext); 78 | serverConfiguration.EventDispatcherFactory = new DefaultStandardEventDispatcherFactory(); 79 | var server = agmFactory.CreateServer(serverConfiguration); 80 | server.Start(); 81 | return server; 82 | } 83 | 84 | private static IClient SetupAgmClient(Properties agmProperties, IApplicationGuiMethods agmFactory, PropertiesSerializationContext propertiesContext) 85 | { 86 | var clientConfiguration = (ClientConfiguration)ConfigurationHelper.LoadClientConfiguration(agmProperties, propertiesContext); 87 | clientConfiguration.EventDispatcherFactory = new DefaultStandardEventDispatcherFactory(); 88 | var client = agmFactory.CreateClient(clientConfiguration); 89 | client.Start(); 90 | return client; 91 | } 92 | 93 | private static Properties GetProperties(string filePath, PropertiesSerializationContext propertiesContext) 94 | { 95 | var properties = PropertyParser.Parse(filePath); 96 | properties.ExpandEnvironmentVariables(propertiesContext.ExpandVariables); 97 | return properties; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /RfqServer.md: -------------------------------------------------------------------------------- 1 | ### UI 2 | 3 | #####RFQ Auto-quote (title) 4 | 5 | **Party**: [ single-select combo w/ les, stoyan ] 6 | **Product**: [ editable text box ] 7 | [ **Subscribe/Unsubscribe** ] // button which toggles - un/subscribes for prices) 8 | 9 | **Best Bid and Offer Prices** 10 | ("rolling" window, showing last 3 prices) 11 | 12 | | Time | Bid | Ask | 13 | |------|-----|-----| 14 | | T | X | Y | (this one highlighted) 15 | | T-1 | X' | Y' | 16 | | T-2 | X" | Y" | 17 | 18 | **Spread**: [ edit box ] // this gets added/subtracted from the top price 19 | 20 | **[ ] Auto-quote** (checkbox) 21 | 22 | **Message history** 23 | 24 | | Time | Request Party | Product | Quantity | 25 | 26 | When party is selected and product is typed, user can click [ Subscribe for prices ], which will actually subscribe for prices on the `T42.MarketStream.Subscribe` stream, as it does on the Portfolio demo. 27 | 28 | While there are not prices, the Auto-quote checkbox will be disabled. As soon as prices start streaming, it will become enabled and when checked on, the app will subscribe for quote requests. 29 | 30 | When an RFQ is received, if it's not for the typed in product, the app should auto-reply "Not quoting product 'ProductName' at the moment". If it's the same product, the app should send a quote response by adjusting the price with the specified spread value: 31 | - if request party is buying, add spread to ask price 32 | - if request party is selling, subtract spread from bid price 33 | 34 | #### Subscribing for RFQs 35 | 36 | The app subscribes for RFQ requests by subscribing on `T42.RFQ.QuoteRequestStream` passing `counterParty`, which should contain the e-mail of the counter party which wants to listen for RFQ requests. 37 | 38 | RFQs will be pushed to that stream and will contain the following information: 39 | 40 | |Field|Description| 41 | |-----|-----------| 42 | |`string requestParty`|E-mail of the party sending the RFQ request| 43 | |`string requestId`|ID of the request, needed to send replies back| 44 | |`string productName`|Name of the product in RFQ, usually an OTC| 45 | |`double quantity`|The number of *units* of the product the requesting party wants to buy or sell. If `quantity` is `> 0` that's a *BUY*, otherwise a *SELL* RFQ request| 46 | |`DateTime requestExpirationDate`|The absolute time when this RFQ expires| 47 | 48 | #### Sending Quotes 49 | 50 | The app can send a quote by calling the `T42.RFQ.SendQuoteResponse`, passing the following parameters: 51 | 52 | |Field|Description| 53 | |-----|-----------| 54 | |`string requestId`|The request ID the app got from the `T42.RFQ.QuoteRequestStream` subscription| 55 | |`string requestParty`|The request party...| 56 | |`string? responseMessage`|Optional, don't send it| 57 | |`string counterParty`|The app's party which sends the response, should equal the `counterParty` from the request| 58 | |`string? productName`|Optional, don't send it| 59 | |`double? quantity`|Optional, don't send it| 60 | |`double price`|The price the app quotes the user| 61 | 62 | #### Receiving from, and sending comments to request party 63 | 64 | The client app can subscribe for request party comments on `T42.RFQ.RequestPartyCommentStream` passing its counter party name. 65 | 66 | The request party comments will be pushed to the stream and will have the following fields: 67 | 68 | |Field|Description| 69 | |-----|-----------| 70 | |`requestParty`|Email of request party| 71 | |`counterParty`|Target counter party's email| 72 | |*`requestId`*|Optional, the request Id of an RFQ for which this comment is about, otherwise the comment is not tied to an RFQ| 73 | |`comment`|Free-text message| 74 | 75 | The counter party can reply at any time, again either to an RFQ-related or general comment. To reply, invoke `T42.RFQ.SendCommentToRequestParty` passing `requestParty`, `counterParty`, `comment`, and optionally the `requestId`. 76 | 77 | ### Appendix - GLUE Method Signatures 78 | 79 | *Note:* All methods start with `T42.RFQ.` 80 | 81 | |Method|Steam?|Accepts|Returns| 82 | |------|------|-------|-------| 83 | |`RequestPartyCommentStream`|Y|`string requestParty`|`string requestParty, string? requestId, string counterParty, comment`| 84 | |`QuoteRequestStream`|Y|`string counterParty`|`string requestId, string requestId, string counterParty, string productName, double quantity, DateTime requestExpirationDate`| 85 | |`SendCommentToRequestParty`|N|`string requestParty, string? requestId, string counterParty, string comment`|| 86 | 87 | 88 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/GlueSymphonyRfqBridge.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1D81E5AE-7B54-4F1C-B4DC-33862D03B7BD} 8 | Exe 9 | Properties 10 | GlueSymphonyRfqBridge 11 | GlueSymphonyRfqBridge 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | anycpu 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\GlueSymphonyRfqBridge\DOT.AGM.dll 39 | 40 | 41 | ..\..\GlueSymphonyRfqBridge\DOT.Core.dll 42 | 43 | 44 | ..\..\GlueSymphonyRfqBridge\DOT.Metrics.dll 45 | 46 | 47 | ..\..\GlueSymphonyRfqBridge\DOT.Transport.dll 48 | 49 | 50 | ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll 51 | True 52 | 53 | 54 | ..\packages\SymphonyOSS.RestApiClient.0.4.0\lib\net45\SymphonyOSS.RestApiClient.dll 55 | True 56 | 57 | 58 | ..\packages\SymphonyOSS.RestApiClient.0.4.0\lib\net45\SymphonyOSS.RestApiClient.Generated.dll 59 | True 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Designer 104 | 105 | 106 | Always 107 | 108 | 109 | Always 110 | 111 | 112 | Always 113 | 114 | 115 | Designer 116 | 117 | 118 | Always 119 | 120 | 121 | 122 | 123 | 130 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Symphony/SymphonyMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Globalization; 6 | using GlueSymphonyRfqBridge.Models; 7 | 8 | namespace GlueSymphonyRfqBridge.Symphony 9 | { 10 | namespace Extensions 11 | { 12 | public static class SymphonyMessageExtensions 13 | { 14 | // rtc stoyan@tick42.com to lspiro@tick42.com re @1 Are you kidding me? 15 | // rtc stoyan@tick42.com to lspiro@tick42.com Are you kidding me? 16 | public static string ToRequestToCounterPartyCommentMessage(this RfqComment comment) 17 | { 18 | if (string.IsNullOrEmpty(comment.RequestId)) 19 | { 20 | return string.Format("rtc {0} to {1} {2}", 21 | comment.RequestParty, 22 | comment.CounterParty, 23 | comment.Comment); 24 | } 25 | return string.Format("rtc {0} to {1} wrt @{2} {3}", 26 | comment.RequestParty, 27 | comment.CounterParty, 28 | comment.RequestId, 29 | comment.Comment); 30 | } 31 | 32 | // ctr lspiro@tick42.com to stoyan@tick42.com re @1 Nope 33 | // ctr lspiro@tick42.com to stoyan@tick42.com Nope 34 | public static string ToCounterToRequestPartyCommentMessage(this RfqComment comment) 35 | { 36 | if (string.IsNullOrEmpty(comment.RequestId)) 37 | { 38 | return string.Format("ctr {0} to {1} {2}", 39 | comment.CounterParty, 40 | comment.RequestParty, 41 | comment.Comment); 42 | } 43 | return string.Format("ctr {0} to {1} wrt @{2} {3}", 44 | comment.CounterParty, 45 | comment.RequestParty, 46 | comment.RequestId, 47 | comment.Comment); 48 | } 49 | 50 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR (15 min) 51 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR 52 | public static string ToMessage( 53 | this RfqQuoteInquiry rfq, 54 | string targetParty) 55 | { 56 | var result = string.Format("rfq {0}:{1} at {2} {3} {4} {5} {6}", 57 | rfq.RequestParty, 58 | rfq.RequestId, 59 | targetParty, 60 | rfq.Quantity > 0 ? "buy" : "sell", 61 | ToHumanReadableQuantity(Math.Abs(rfq.Quantity)), 62 | rfq.ProductName, 63 | ToHumanReadableExpiry(rfq.RequestExpirationDate)); 64 | return result; 65 | } 66 | 67 | public static string ToMessage(this RfqQuoteInquiryResponse quote) 68 | { 69 | switch (quote.ResponseType) 70 | { 71 | // this one should not happen 72 | case RfqResponseType.SetRequestId: 73 | return string.Format("id {0}:{1}", quote.RequestParty, quote.RequestId); 74 | case RfqResponseType.Expired: 75 | return string.Format("expired {0}:{1}", quote.RequestParty, quote.RequestId); 76 | case RfqResponseType.Error: 77 | return string.Format("error {0}:{1} {2}", quote.RequestParty, quote.RequestId, quote.ResponseMessage); 78 | case RfqResponseType.Quote: 79 | return ToQuoteMessage(quote); 80 | default: 81 | throw new NotImplementedException(); 82 | } 83 | } 84 | 85 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.3 for 200m 86 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.5 87 | private static string ToQuoteMessage(RfqQuoteInquiryResponse quote) 88 | { 89 | var result = string.Format("quote from {0} to {1}:{2} @ {3}", 90 | quote.CounterParty, 91 | quote.RequestParty, 92 | quote.RequestId, 93 | string.Format(CultureInfo.InvariantCulture, "{0:N4}", quote.Price.Value)); 94 | if (quote.Quantity.HasValue) 95 | { 96 | result += " for " + ToHumanReadableQuantity(quote.Quantity.Value); 97 | } 98 | return result; 99 | } 100 | 101 | private static string ToHumanReadableQuantity(double quantity) 102 | { 103 | // TODO: deal with floating numbers; for now just assume longs 104 | var qty = Math.Abs((long)Math.Truncate(quantity)); 105 | if (qty > 1000000 && (qty % 1000000) == 0) 106 | { 107 | return (qty / 1000000) + "m"; 108 | } 109 | if (qty > 1000 && (qty % 1000) == 0) 110 | { 111 | return (qty / 1000) + "k"; 112 | } 113 | return string.Format(CultureInfo.InvariantCulture, "{0:N0}", quantity); 114 | } 115 | 116 | private static string ToHumanReadableExpiry(DateTime dateTime) 117 | { 118 | var span = dateTime - DateTime.UtcNow; 119 | 120 | var mins = (long)span.TotalMinutes; 121 | if (mins >= 60 && (mins % 60) == 0) 122 | { 123 | var hours = mins / 60; 124 | return string.Format("({0} hour{1})", 125 | hours, 126 | hours == 1 ? string.Empty : "s"); 127 | } 128 | 129 | var secs = (long)span.TotalSeconds; 130 | if (secs >= 60 && (secs % 60) == 0) 131 | { 132 | mins = secs / 60; 133 | return string.Format("({0} min)", mins); 134 | } 135 | return string.Format("({0} sec)", secs); 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /JavaScriptApps/client/src/App.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 200 | 201 | 220 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Glue/GlueRfqBridge.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using DOT.Logging; 7 | using GlueSymphonyRfqBridge.Models; 8 | 9 | namespace GlueSymphonyRfqBridge.Glue 10 | { 11 | public class GlueRfqBridge : IGlueRfqBridge 12 | { 13 | private static readonly ISmartLogger Logger = new SmartLogger(typeof(GlueRfqBridge)); 14 | 15 | private readonly object mx_ = new object(); 16 | 17 | // counterParty 18 | private readonly HashSet counterToRequestPartyCommentSubscriptions_ = 19 | new HashSet(); 20 | private readonly HashSet quoteRequestSubscriptions_ = 21 | new HashSet(); 22 | // requestParty 23 | private readonly HashSet requestToCounterPartyCommentSubscriptions_ = 24 | new HashSet(); 25 | 26 | // NB: we're not keeping a set of request subscriptions because 27 | // for the moment we can't figure out when a request has 28 | // ended so we can delete it from the set; once we start 29 | // tracking expirations, etc. we'll be able to do it 30 | // Besides, this is a loopback bridge for testing purposes 31 | 32 | public string Name 33 | { 34 | get 35 | { 36 | return "Glue"; 37 | } 38 | } 39 | 40 | public void Start() 41 | { 42 | } 43 | 44 | public void Stop() 45 | { 46 | } 47 | 48 | public event EventHandler> CounterPartyCommentReceived; 49 | public event EventHandler> QuoteInquiryResponseReceived; 50 | public event EventHandler> QuoteRequestReceived; 51 | public event EventHandler> RequestPartyCommentReceived; 52 | 53 | public void SendCommentToCounterParty(RfqComment comment) 54 | { 55 | Logger.InfoFormat("Sending comment {0}", comment); 56 | 57 | lock (mx_) 58 | { 59 | if (!counterToRequestPartyCommentSubscriptions_.Contains(comment.CounterParty)) 60 | { 61 | // TODO: log we're ignoring 62 | return; 63 | } 64 | } 65 | 66 | RaiseRequestPartyCommentReceived(comment); 67 | } 68 | 69 | public void SendCommentToRequestParty(RfqComment comment) 70 | { 71 | Logger.InfoFormat("Sending comment {0}", comment); 72 | 73 | lock (mx_) 74 | { 75 | if (!requestToCounterPartyCommentSubscriptions_.Contains(comment.RequestParty)) 76 | { 77 | // TODO: log we're ignoring 78 | return; 79 | } 80 | } 81 | 82 | RaiseCounterPartyCommentReceived(comment); 83 | } 84 | 85 | public void SendQuoteInquiry(RfqQuoteInquiry quoteInquiry) 86 | { 87 | Logger.InfoFormat("Sending quote inquiry {0}", quoteInquiry); 88 | 89 | var handler = QuoteRequestReceived; 90 | if (handler == null) 91 | { 92 | return; 93 | } 94 | 95 | foreach (var counterParty in quoteInquiry.CounterParties) 96 | { 97 | handler(this, new Event( 98 | new RfqQuoteRequest( 99 | quoteInquiry.RequestId, 100 | quoteInquiry.RequestParty, 101 | counterParty, 102 | quoteInquiry.ProductName, 103 | quoteInquiry.ProductDetails, 104 | quoteInquiry.Quantity, 105 | quoteInquiry.RequestExpirationDate))); 106 | } 107 | } 108 | 109 | private void RaiseQuoteInquiryResponseReceived(RfqQuoteInquiryResponse response) 110 | { 111 | Logger.InfoFormat("Quote inquiry response received: {0}", response); 112 | 113 | var handler = QuoteInquiryResponseReceived; 114 | if (handler != null) 115 | { 116 | handler(this, new Event(response)); 117 | } 118 | } 119 | 120 | public void SubscribeForRequestPartyComments(string counterParty) 121 | { 122 | Logger.InfoFormat("Subscribing counter party {0} for request party comments", counterParty); 123 | 124 | lock (mx_) 125 | { 126 | counterToRequestPartyCommentSubscriptions_.Add(counterParty); 127 | } 128 | } 129 | 130 | private void RaiseRequestPartyCommentReceived(RfqComment comment) 131 | { 132 | Logger.InfoFormat("Request party comment received: {0}", comment); 133 | 134 | var handler = RequestPartyCommentReceived; 135 | if (handler != null) 136 | { 137 | handler(this, new Event(comment)); 138 | } 139 | } 140 | 141 | public void UnsubscribeForRequestPartyComments(string counterParty) 142 | { 143 | Logger.InfoFormat("Unsubscribing counter party {0} for request party comments", counterParty); 144 | 145 | lock (mx_) 146 | { 147 | counterToRequestPartyCommentSubscriptions_.Remove(counterParty); 148 | } 149 | } 150 | 151 | public void SubscribeForCounterPartyComments(string requestParty) 152 | { 153 | Logger.InfoFormat("Subscribing request party {0} for counter party comments", requestParty); 154 | 155 | lock (mx_) 156 | { 157 | requestToCounterPartyCommentSubscriptions_.Add(requestParty); 158 | } 159 | } 160 | 161 | private void RaiseCounterPartyCommentReceived(RfqComment comment) 162 | { 163 | Logger.InfoFormat("Counter party comment received: {0}", comment); 164 | 165 | var handler = CounterPartyCommentReceived; 166 | if (handler != null) 167 | { 168 | handler(this, new Event(comment)); 169 | } 170 | } 171 | 172 | public void UnsubscribeForCounterPartyComments(string requestParty) 173 | { 174 | Logger.InfoFormat("Unsubscribing request party {0} for counter party comments", requestParty); 175 | 176 | lock (mx_) 177 | { 178 | requestToCounterPartyCommentSubscriptions_.Remove(requestParty); 179 | } 180 | } 181 | 182 | public void SubscribeForQuoteRequests(string counterParty) 183 | { 184 | Logger.InfoFormat("Subscribing counter party {0} for request party RFQ requests", counterParty); 185 | 186 | lock (mx_) 187 | { 188 | quoteRequestSubscriptions_.Add(counterParty); 189 | } 190 | } 191 | 192 | private void RaiseQuoteRequestReceived(RfqQuoteRequest quoteRequest) 193 | { 194 | Logger.InfoFormat("Quote request received: {0}", quoteRequest); 195 | 196 | lock (mx_) 197 | { 198 | if (!quoteRequestSubscriptions_.Contains(quoteRequest.CounterParty)) 199 | { 200 | return; 201 | } 202 | } 203 | 204 | var handler = QuoteRequestReceived; 205 | if (handler != null) 206 | { 207 | handler(this, new Event(quoteRequest)); 208 | } 209 | } 210 | 211 | public void UnsubscribeForQuoteRequests(string counterParty) 212 | { 213 | Logger.InfoFormat("Unsubscribing counter party {0} for request party RFQ requests", counterParty); 214 | 215 | lock (mx_) 216 | { 217 | quoteRequestSubscriptions_.Remove(counterParty); 218 | } 219 | } 220 | 221 | public void SendQuoteInquiryResponse(RfqQuoteInquiryResponse quoteInquiryResponse) 222 | { 223 | Logger.InfoFormat("Sending quote inquiry response {0}", quoteInquiryResponse); 224 | 225 | RaiseQuoteInquiryResponseReceived(quoteInquiryResponse); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /JavaScriptApps/server/src/App.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 290 | -------------------------------------------------------------------------------- /GlueSymphonyRfqBridge/Symphony/SymphonyRfqBridge.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Tick42 OOD 2 | // -- COPYRIGHT END -- 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Security.Cryptography.X509Certificates; 8 | using System.Text.RegularExpressions; 9 | using System.Threading.Tasks; 10 | using DOT.Logging; 11 | using GlueSymphonyRfqBridge.Extensions; 12 | using GlueSymphonyRfqBridge.Models; 13 | using GlueSymphonyRfqBridge.Models.Extensions; 14 | using GlueSymphonyRfqBridge.Symphony.Extensions; 15 | using SymphonyOSS.RestApiClient.Api.AgentApi; 16 | using SymphonyOSS.RestApiClient.Api.PodApi; 17 | using SymphonyOSS.RestApiClient.Authentication; 18 | using SymphonyOSS.RestApiClient.Entities; 19 | using SymphonyOSS.RestApiClient.Factories; 20 | using SymphonyOSS.RestApiClient.MessageML; 21 | 22 | namespace GlueSymphonyRfqBridge.Symphony 23 | { 24 | // NB: the bridge doesn't hold any state, except who's supposed to receive and what kind of messages 25 | public class SymphonyRfqBridge : IGlueRfqBridge 26 | { 27 | private static ISmartLogger Logger = new SmartLogger(typeof(SymphonyRfqBridge)); 28 | 29 | #region Command Parsing Regular Expressions 30 | 31 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR (15 min) 32 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR 33 | private static Regex PatternRequest = new Regex( 34 | @"(?[^:]+):(?\d+)\s+at\s+(?\S+)\s+(?buy|sell)\s+(?\d+(,\d+)*(?:k|m)*)\s+(?\S+)(?:\s+[(](?(\d+)\s+(min|m|sec|s|hour|hours|h))[)])?", 35 | RegexOptions.IgnoreCase); 36 | 37 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.3 for 200m 38 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.5 39 | private static Regex PatternResponse = new Regex( 40 | @"from\s(?\S+)\s+to\s+(?[^:]+):(?\d+)\s+@\s+(?\d+(?:\.\d+)*)(?:\s+for\s(?\d+(,\d+)*(?:k|m)*))?", 41 | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 42 | 43 | // expired stoyan@tick42.com:1 44 | private static Regex PatternExpired = new Regex( 45 | @"(?[^:]+):(?\d+)", 46 | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 47 | 48 | // error stoyan@tick42.com:1 Wrong RFQ request 49 | private static Regex PatternError = new Regex( 50 | @"(?[^:]+):(?\d+)\s+(?.+)", 51 | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 52 | 53 | // rtc stoyan@tick42.com to lspiro@tick42.com re @1 Are you kidding me? 54 | // rtc stoyan@tick42.com to lspiro@tick42.com Are you kidding me? 55 | private static Regex PatternRequestToCounterPartyComment = new Regex( 56 | @"(?\S+)\s+to\s+(?\S+)(?:\s+(?:re|wrt)\s+@(?\d+))?\s+(?.+)", 57 | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 58 | 59 | // ctr lspiro@tick42.com to stoyan@tick42.com re @1 Nope 60 | // ctr lspiro@tick42.com to stoyan@tick42.com Nope 61 | private static Regex PatternCounterToRequestPartyComment = new Regex( 62 | @"(?\S+)\s+to\s+(?\S+)(?:\s+(?:re|wrt)\s+@(?\d+))?\s+(?.+)", 63 | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 64 | 65 | #endregion Command Parsing Regular Expressions 66 | 67 | private readonly SymphonyRfqBridgeConfiguration config_; 68 | 69 | private MessagesApi messagesApi_; 70 | private StreamsApi streamsApi_; 71 | private UsersApi usersApi_; 72 | private DatafeedApi datafeedApi_; 73 | 74 | private readonly object userCacheMx_ = new object(); 75 | private readonly Dictionary usersByEmail_ = new Dictionary(); 76 | private readonly Dictionary usersById_ = new Dictionary(); 77 | 78 | private readonly Dictionary> commandHandlers_ = 79 | new Dictionary>(); 80 | 81 | private readonly object mx_ = new object(); 82 | private HashSet counterPartiesTrackingCommentsFromRequestParties_ = new HashSet(); 83 | private HashSet counterPartiesTrackingRequestsFromRequestParties_ = new HashSet(); 84 | private HashSet requestPartiesTrackingRequestsFromCounterParties_ = new HashSet(); 85 | 86 | public SymphonyRfqBridge(SymphonyRfqBridgeConfiguration config) 87 | { 88 | config_ = config; 89 | 90 | commandHandlers_[RfqCommand.Help] = HandleHelpCommand; 91 | commandHandlers_[RfqCommand.Error] = HandleErrorCommand; 92 | commandHandlers_[RfqCommand.Request] = HandleRequestCommand; 93 | commandHandlers_[RfqCommand.Response] = HandleResponseCommand; 94 | commandHandlers_[RfqCommand.Expired] = HandleExpiredCommand; 95 | commandHandlers_[RfqCommand.RequestToCounterPartyComment] = HandleRequestToCounterPartyCommentCommand; 96 | commandHandlers_[RfqCommand.CounterToRequestPartyComment] = HandleCounterPartyToRequestCommentCommand; 97 | } 98 | 99 | public string Name 100 | { 101 | get 102 | { 103 | return "Symphony"; 104 | } 105 | } 106 | 107 | public event EventHandler> CounterPartyCommentReceived; 108 | public event EventHandler> QuoteInquiryResponseReceived; 109 | public event EventHandler> QuoteRequestReceived; 110 | public event EventHandler> RequestPartyCommentReceived; 111 | 112 | public void Start() 113 | { 114 | Logger.InfoFormat("Starting up with {0}...", config_); 115 | 116 | StartupSymphonyBot(); 117 | } 118 | 119 | public void Stop() 120 | { 121 | Logger.Info("Stopping..."); 122 | 123 | StopSymphonyBot(); 124 | } 125 | 126 | public void SubscribeForRequestPartyComments(string counterParty) 127 | { 128 | Logger.InfoFormat("Subscribing counter party {0} for request party comments", counterParty); 129 | 130 | lock (mx_) 131 | { 132 | counterPartiesTrackingCommentsFromRequestParties_.Add(counterParty); 133 | } 134 | } 135 | 136 | public void UnsubscribeForRequestPartyComments(string counterParty) 137 | { 138 | Logger.InfoFormat("Unsubscribing counter party {0} for request party comments", counterParty); 139 | 140 | lock (mx_) 141 | { 142 | counterPartiesTrackingCommentsFromRequestParties_.Remove(counterParty); 143 | } 144 | } 145 | 146 | public void SubscribeForQuoteRequests(string counterParty) 147 | { 148 | Logger.InfoFormat("Subscribing counter party {0} for request party RFQ requests", counterParty); 149 | 150 | lock (mx_) 151 | { 152 | counterPartiesTrackingRequestsFromRequestParties_.Add(counterParty); 153 | } 154 | } 155 | 156 | public void UnsubscribeForQuoteRequests(string counterParty) 157 | { 158 | Logger.InfoFormat("Unsubscribing counter party {0} for request party RFQ requests", counterParty); 159 | 160 | lock (mx_) 161 | { 162 | counterPartiesTrackingRequestsFromRequestParties_.Remove(counterParty); 163 | } 164 | } 165 | 166 | public void SendCommentToCounterParty(RfqComment comment) 167 | { 168 | Logger.InfoFormat("Sending comment {0}", comment); 169 | 170 | SendMessage(comment.CounterParty, comment.ToRequestToCounterPartyCommentMessage()); 171 | } 172 | 173 | public void SendCommentToRequestParty(RfqComment comment) 174 | { 175 | Logger.InfoFormat("Sending comment {0}", comment); 176 | 177 | SendMessage(comment.RequestParty, comment.ToCounterToRequestPartyCommentMessage()); 178 | } 179 | 180 | public void SendQuoteInquiry(RfqQuoteInquiry quoteInquiry) 181 | { 182 | Logger.InfoFormat("Sending quote inquiry {0}", quoteInquiry); 183 | 184 | foreach (var party in quoteInquiry.CounterParties) 185 | { 186 | SendMessage(party, quoteInquiry.ToMessage(party)); 187 | } 188 | } 189 | 190 | public void SendQuoteInquiryResponse(RfqQuoteInquiryResponse quoteInquiryResponse) 191 | { 192 | Logger.InfoFormat("Sending quote inquiry response {0}", quoteInquiryResponse); 193 | 194 | SendMessage(quoteInquiryResponse.RequestParty, quoteInquiryResponse.ToMessage()); 195 | } 196 | 197 | private void RaiseQuoteInquiryResponseReceived(RfqQuoteInquiryResponse response) 198 | { 199 | Logger.InfoFormat("Quote inquiry response received: {0}", response); 200 | 201 | var handler = QuoteInquiryResponseReceived; 202 | if (handler != null) 203 | { 204 | handler(this, new Event(response)); 205 | } 206 | } 207 | 208 | private void RaiseCounterPartyCommentReceived(RfqComment comment) 209 | { 210 | Logger.InfoFormat("Counter party comment received: {0}", comment); 211 | 212 | var handler = CounterPartyCommentReceived; 213 | if (handler != null) 214 | { 215 | handler(this, new Event(comment)); 216 | } 217 | } 218 | 219 | private void RaiseQuoteRequestReceived(RfqQuoteRequest request) 220 | { 221 | Logger.InfoFormat("Quote request received: {0}", request); 222 | 223 | var handler = QuoteRequestReceived; 224 | if (handler != null) 225 | { 226 | handler(this, new Event(request)); 227 | } 228 | } 229 | 230 | private void RaiseRequestPartyCommentReceived(RfqComment comment) 231 | { 232 | Logger.InfoFormat("Request party comment received: {0}", comment); 233 | 234 | var handler = RequestPartyCommentReceived; 235 | if (handler != null) 236 | { 237 | handler(this, new Event(comment)); 238 | } 239 | } 240 | 241 | // Symphony-specific implementation 242 | 243 | private void OnMessageReceived(object sender, MessageEventArgs e) 244 | { 245 | if (Logger.IsDebugEnabled) 246 | { 247 | Logger.DebugFormat("Received {0}", e.Message); 248 | } 249 | 250 | var msg = e.Message; 251 | if (msg == null) 252 | { 253 | Logger.WarnFormat("Ignoring non-V2Message message: {0}", e.Message); 254 | return; 255 | } 256 | var message = new MessageParser().GetPlainText(msg.Body); 257 | var userId = msg.FromUserId; 258 | if (userId == -1) 259 | { 260 | Logger.WarnFormat("Ignoring message without user ID: {0}", e.Message); 261 | return; 262 | } 263 | User user; 264 | if (!TryGetUserById(userId, out user)) 265 | { 266 | Logger.WarnFormat("Ignoring message, can't resolve user with ID {0}: {1}", userId, e.Message); 267 | return; 268 | } 269 | if (Logger.IsDebugEnabled) 270 | { 271 | Logger.DebugFormat("Received (from {0}): {1}", user.EmailAddress, message); 272 | } 273 | try 274 | { 275 | var command = ParseMessage(message); 276 | if (command == null) 277 | { 278 | throw new InvalidOperationException("Internal error - failed parsing command " + message); 279 | } 280 | var handler = commandHandlers_[command.Command]; 281 | handler(command, user); 282 | } 283 | catch (Exception ex) 284 | { 285 | Logger.Warn( 286 | string.Format("Unexpected error while processing message: {0}", ex.Message), 287 | ex); 288 | } 289 | } 290 | 291 | public static BotCommand ParseMessage(string message) 292 | { 293 | // get the 1st token 294 | var tokenEndPos = message.IndexOf(' '); 295 | if (tokenEndPos < 0) 296 | { 297 | return new BotCommand(RfqCommand.Help); 298 | } 299 | var token = message.Substring(0, tokenEndPos).Trim(); 300 | 301 | // no token (so empty string) or "help" -> help 302 | if (token.Length == 0 || 303 | token.Equals("help", StringComparison.InvariantCultureIgnoreCase)) 304 | { 305 | return new BotCommand(RfqCommand.Help); 306 | } 307 | 308 | var rest = message.Substring(tokenEndPos).Trim(); 309 | if (token.Equals("rfq", StringComparison.InvariantCultureIgnoreCase)) 310 | { 311 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR (15 min) 312 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR 313 | return ParseCommandArgs(PatternRequest, RfqCommand.Request, rest, 314 | "requestParty", "requestId", "counterParty", "action", 315 | "quantity", "product", "requestExpirationDate"); 316 | } 317 | if (token.Equals("quote", StringComparison.InvariantCultureIgnoreCase)) 318 | { 319 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.3 for 200m 320 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.5 321 | return ParseCommandArgs(PatternResponse, RfqCommand.Response, rest, 322 | "requestParty", "requestId", "counterParty", "price", "quantity"); 323 | } 324 | if (token.Equals("expired", StringComparison.InvariantCultureIgnoreCase)) 325 | { 326 | // expired stoyan@tick42.com:1 327 | return ParseCommandArgs(PatternExpired, RfqCommand.Expired, rest, 328 | "party", "requestId"); 329 | } 330 | if (token.Equals("error", StringComparison.InvariantCultureIgnoreCase)) 331 | { 332 | // error stoyan@tick42.com:1 Wrong RFQ request 333 | return ParseCommandArgs(PatternError, RfqCommand.Error, rest, 334 | "party", "requestId", "message"); 335 | } 336 | if (token.Equals("rtc", StringComparison.InvariantCultureIgnoreCase)) 337 | { 338 | // rtc stoyan@tick42.com to lspiro@tick42.com re @1 Are you kidding me? 339 | // rtc stoyan@tick42.com to lspiro@tick42.com Are you kidding me? 340 | return ParseCommandArgs(PatternRequestToCounterPartyComment, 341 | RfqCommand.RequestToCounterPartyComment, 342 | rest, 343 | "requestParty", "requestId", "counterParty", "comment"); 344 | } 345 | if (token.Equals("ctr", StringComparison.InvariantCultureIgnoreCase)) 346 | { 347 | // ctr lspiro@tick42.com to stoyan@tick42.com re @1 Nope 348 | // ctr lspiro@tick42.com to stoyan@tick42.com Nope 349 | return ParseCommandArgs(PatternCounterToRequestPartyComment, 350 | RfqCommand.CounterToRequestPartyComment, 351 | rest, 352 | "requestParty", "requestId", "counterParty", "comment"); 353 | } 354 | 355 | return new BotCommand(RfqCommand.Help); 356 | } 357 | 358 | private static BotCommand ParseCommandArgs( 359 | Regex pattern, 360 | RfqCommand command, 361 | string argumentsText, 362 | params string[] parameters) 363 | { 364 | var matches = pattern.Matches(argumentsText); 365 | if (matches.Count != 1) 366 | { 367 | return null; 368 | } 369 | return new BotCommand(command) 370 | .WithRegexGroup( 371 | matches[0].Groups, 372 | parameters); 373 | } 374 | 375 | private void HandleHelpCommand(BotCommand cmd, User sender) 376 | { 377 | SendMessage(sender, @" 378 | 379 | GLUE RFQ Chat Bot Messages By Example 380 | 381 | 1. Request party issues an RFQ (request for quote) request 382 | 383 | rfq REQUEST_PARTY:REQUEST_ID at COUNTER_PARTY buy|sell QUANTITY PRODUCT_NAME (REQUEST_EXPIRY) 384 | rfq alice@request.com:123 at bob@response.com buy 100m GBP/EUR (15 min) 385 | 386 | 2. Counter party quotes requesting party 387 | 388 | quote from COUNTER_PARTY to REQUEST_PARTY:REQUEST_ID @ PRICE 389 | quote from bob@response.com to alice@request:123 @ 57.3 390 | 391 | 3. Requesting party sends a comment to counter party 392 | 393 | rtc REQUEST_PARTY to COUNTER_PARTY re|wrt @REQUEST_ID @COMMENT 394 | rtc alice@request.com to bob@response.com re @123 Are you kidding me? 395 | rtc alice@request.com to bob@response.com Are you kidding me? 396 | 397 | 4. Counter party to request party comment 398 | 399 | ctr COUNTER_PARTY to REQUEST_PARTY re|wrt @REQUEST_ID @COMMENT 400 | ctr bob@response.com to alice@request.com re @1 Nope 401 | ctr bob@response.com to alice@request.com Nope 402 | "); 403 | } 404 | 405 | private void HandleErrorCommand(BotCommand cmd, User sender) 406 | { 407 | // error stoyan@tick42.com:1 Wrong RFQ request 408 | RaiseQuoteInquiryResponseReceived( 409 | new RfqQuoteInquiryResponse( 410 | RfqResponseType.Error, 411 | cmd["message"], 412 | cmd["requestId"], 413 | cmd["party"], 414 | sender.EmailAddress, 415 | null, 416 | null, 417 | null, 418 | null)); 419 | } 420 | 421 | private void HandleRequestCommand(BotCommand cmd, User sender) 422 | { 423 | var quantitySign = cmd["action"].Equals("buy", StringComparison.InvariantCultureIgnoreCase) ? 424 | 1 : -1; 425 | var request = new RfqQuoteRequest( 426 | cmd["requestId"], 427 | cmd["requestParty"], 428 | cmd["counterParty"], 429 | cmd["product"], 430 | null, 431 | quantitySign * ParseQuantity(cmd["quantity"]).Value, 432 | ParseExpiry(cmd.Get("requestExpirationDate"))); 433 | 434 | lock (mx_) 435 | { 436 | if (!counterPartiesTrackingRequestsFromRequestParties_.Contains(request.CounterParty)) 437 | { 438 | return; 439 | } 440 | } 441 | 442 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR (15 min) 443 | // rfq stoyan@tick42.com:1 at lspiro@tick42.com buy 100m GBP/EUR 444 | RaiseQuoteRequestReceived(request); 445 | } 446 | 447 | private void HandleResponseCommand(BotCommand cmd, User sender) 448 | { 449 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.3 for 200m 450 | // quote from lspiro@tick42.com to stoyan@tick42.com:1 @ 57.5 451 | RaiseQuoteInquiryResponseReceived( 452 | new RfqQuoteInquiryResponse( 453 | RfqResponseType.Quote, 454 | null, 455 | cmd["requestId"], 456 | cmd["requestParty"], 457 | cmd["counterParty"], 458 | cmd.Get("product"), 459 | null, 460 | ParseQuantity(cmd.Get("quantity")), 461 | ParsePrice(cmd["price"]))); 462 | } 463 | 464 | private void HandleExpiredCommand(BotCommand cmd, User sender) 465 | { 466 | RaiseQuoteInquiryResponseReceived( 467 | new RfqQuoteInquiryResponse( 468 | RfqResponseType.Expired, 469 | null, 470 | cmd["requestId"], 471 | cmd["party"], 472 | sender.EmailAddress, 473 | null, 474 | null, 475 | null, 476 | null)); 477 | } 478 | 479 | private void HandleRequestToCounterPartyCommentCommand(BotCommand cmd, User sender) 480 | { 481 | var comment = ParseComment(cmd); 482 | 483 | lock (mx_) 484 | { 485 | if (!counterPartiesTrackingCommentsFromRequestParties_.Contains(comment.CounterParty)) 486 | { 487 | Logger.InfoFormat("Ignoring comment for {0}, not subscribed as this party for request party comments", comment.CounterParty); 488 | return; 489 | } 490 | } 491 | 492 | RaiseRequestPartyCommentReceived(comment); 493 | } 494 | 495 | private void HandleCounterPartyToRequestCommentCommand(BotCommand cmd, User sender) 496 | { 497 | var comment = ParseComment(cmd); 498 | 499 | lock (mx_) 500 | { 501 | if (!requestPartiesTrackingRequestsFromCounterParties_.Contains(comment.RequestParty)) 502 | { 503 | Logger.InfoFormat("Ignoring comment for {0}, not subscribed as this party for counter party comments", comment.RequestParty); 504 | return; 505 | } 506 | } 507 | 508 | RaiseCounterPartyCommentReceived(comment); 509 | } 510 | 511 | private static RfqComment ParseComment(BotCommand command) 512 | { 513 | return new RfqComment( 514 | command.Get("requestId"), 515 | command["requestParty"], 516 | command["counterParty"], 517 | null, 518 | command["comment"]); 519 | } 520 | 521 | public static double? ParseQuantity(string text) 522 | { 523 | if (string.IsNullOrEmpty(text)) 524 | { 525 | return null; 526 | } 527 | var factor = 1; 528 | if (text.EndsWith("k", StringComparison.InvariantCultureIgnoreCase)) 529 | { 530 | text = text.Remove(text.Length - 1); 531 | factor = 1000; 532 | } 533 | else if (text.EndsWith("m", StringComparison.InvariantCultureIgnoreCase)) 534 | { 535 | text = text.Remove(text.Length - 1); 536 | factor = 1000000; 537 | } 538 | return factor * text.ParseDouble(); 539 | } 540 | 541 | public static double ParsePrice(string text) 542 | { 543 | return text.ParseDouble(); 544 | } 545 | 546 | private DateTime ParseExpiry(string text) 547 | { 548 | var now = DateTime.UtcNow; 549 | if (string.IsNullOrEmpty(text)) 550 | { 551 | return now.Add(config_.DefaultRfqExpiry); 552 | } 553 | var parts = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 554 | var value = int.Parse(parts[0], NumberStyles.Integer | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture); 555 | var suffix = parts[1]; 556 | if (suffix.StartsWith("s", StringComparison.CurrentCultureIgnoreCase)) 557 | { 558 | return now.AddSeconds(value); 559 | } 560 | if (suffix.StartsWith("m", StringComparison.CurrentCultureIgnoreCase)) 561 | { 562 | return now.AddMinutes(value); 563 | } 564 | if (suffix.StartsWith("h", StringComparison.CurrentCultureIgnoreCase)) 565 | { 566 | return now.AddHours(value); 567 | } 568 | throw new ArgumentException("Invalid RFQ expiration " + text); 569 | } 570 | 571 | private void StartupSymphonyBot() 572 | { 573 | // Symphony start up boilerplate start 574 | 575 | SymphonyOSS.RestApiClient.Generated.OpenApi.PodApi.Client.Configuration.Default = 576 | new SymphonyOSS.RestApiClient.Generated.OpenApi.PodApi.Client.Configuration(timeout: config_.TimeoutInMillis); 577 | SymphonyOSS.RestApiClient.Generated.OpenApi.AuthenticatorApi.Client.Configuration.Default = 578 | new SymphonyOSS.RestApiClient.Generated.OpenApi.AuthenticatorApi.Client.Configuration(timeout: config_.TimeoutInMillis); 579 | SymphonyOSS.RestApiClient.Generated.OpenApi.AgentApi.Client.Configuration.Default = 580 | new SymphonyOSS.RestApiClient.Generated.OpenApi.AgentApi.Client.Configuration(timeout: config_.TimeoutInMillis); 581 | 582 | var certificate = new X509Certificate2( 583 | config_.BotCertificateFilePath, 584 | config_.BotCertificatePassword); 585 | var sessionManager = new UserSessionManager( 586 | string.Format("{0}/sessionauth/", config_.BaseApiUrl), 587 | string.Format("{0}/keyauth/", config_.BaseApiUrl), 588 | certificate); 589 | var agentApiFactory = new AgentApiFactory( 590 | string.Format("{0}/agent", config_.BaseApiUrl)); 591 | var podApiBaseUrl = string.Format("{0}/pod", config_.BasePodUrl); 592 | var podApiFactory = new PodApiFactory(podApiBaseUrl); 593 | 594 | // Symphony start up boilerplate end 595 | 596 | // create a data feed API to listen for chat messages 597 | datafeedApi_ = agentApiFactory.CreateDatafeedApi(sessionManager); 598 | datafeedApi_.OnMessage += OnMessageReceived; 599 | 600 | // create streams API to initiate chats 601 | streamsApi_ = podApiFactory.CreateStreamsApi(sessionManager); 602 | 603 | // create messages API to send messages 604 | messagesApi_ = agentApiFactory.CreateMessagesApi(sessionManager); 605 | 606 | // create users API to resolve users by e-mail or id 607 | usersApi_ = podApiFactory.CreateUsersApi(sessionManager); 608 | 609 | // start listening for messages from out bot 610 | // NB: Listen() is blocking, so run on a dedicated thread 611 | Task.Factory.StartNew(datafeedApi_.Listen, TaskCreationOptions.LongRunning); 612 | } 613 | 614 | private void StopSymphonyBot() 615 | { 616 | datafeedApi_.Stop(); 617 | } 618 | 619 | // 620 | 621 | private void SendMessage(string partyName, string messageText) 622 | { 623 | User user; 624 | if (!TryGetUser(partyName, out user)) 625 | { 626 | throw new Exception("Could not resolve user " + partyName); 627 | } 628 | SendMessage(user, messageText); 629 | } 630 | 631 | private void SendMessage(User user, string messageText) 632 | { 633 | string streamId; 634 | try 635 | { 636 | streamId = streamsApi_.CreateStream(new List { user.Id }); 637 | } 638 | catch (Exception e) 639 | { 640 | Logger.Error(string.Format("Failed to create chat with {0}: {1}", 641 | user.EmailAddress, 642 | e), 643 | e); 644 | throw new Exception("Failed to create chat with " + user.EmailAddress, e); 645 | } 646 | 647 | Logger.InfoFormat("Sending {0} to {1}", messageText, user.EmailAddress); 648 | 649 | try 650 | { 651 | var message = new Message(streamId, MessageFormat.Text, messageText); 652 | messagesApi_.PostMessage(message); 653 | } 654 | catch (Exception e) 655 | { 656 | Logger.Error(string.Format("Failed to send {0} to {1}: {2}", 657 | messageText, 658 | user.EmailAddress, 659 | e), 660 | e); 661 | throw new Exception("Failed to send " + messageText + " to " + user.EmailAddress, e); 662 | } 663 | } 664 | 665 | private bool TryGetUser(string partyName, out User user) 666 | { 667 | lock (userCacheMx_) 668 | { 669 | if (usersByEmail_.TryGetValue(partyName, out user)) 670 | { 671 | return true; 672 | } 673 | try 674 | { 675 | var userId = usersApi_.GetUserId(partyName); 676 | if (userId == -1) 677 | { 678 | Logger.WarnFormat("Could not resolve user " + partyName); 679 | throw new Exception("User " + partyName + " could not be resolved"); 680 | } 681 | else 682 | { 683 | user = usersApi_.GetUser(userId); 684 | } 685 | usersByEmail_.Add(partyName, user); 686 | usersById_.Add(user.Id, user); 687 | return true; 688 | } 689 | catch (Exception e) 690 | { 691 | Console.WriteLine("WARN: Could not resolve user {0}: {1}", partyName, e.ToString()); 692 | return false; 693 | } 694 | } 695 | } 696 | 697 | private bool TryGetUserById(long id, out User user) 698 | { 699 | user = null; 700 | lock (userCacheMx_) 701 | { 702 | if (usersById_.TryGetValue(id, out user)) 703 | { 704 | return true; 705 | } 706 | try 707 | { 708 | user = usersApi_.GetUser(id); 709 | if (user == null || user.EmailAddress == null) 710 | { 711 | throw new Exception("User " + id + " could not be resolved"); 712 | } 713 | usersByEmail_.Add(user.EmailAddress, user); 714 | usersById_.Add(id, user); 715 | return true; 716 | } 717 | catch (Exception e) 718 | { 719 | Logger.Warn(string.Format("Could not resolve user {0}: {1}", id, e), e); 720 | return false; 721 | } 722 | } 723 | } 724 | 725 | public void SubscribeForCounterPartyComments(string requestParty) 726 | { 727 | Logger.InfoFormat("Subscribing request party {0} for counter party comments", requestParty); 728 | 729 | lock (mx_) 730 | { 731 | requestPartiesTrackingRequestsFromCounterParties_.Add(requestParty); 732 | } 733 | } 734 | 735 | public void UnsubscribeForCounterPartyComments(string requestParty) 736 | { 737 | Logger.InfoFormat("Unsubscribing request party {0} for counter party comments", requestParty); 738 | 739 | lock (mx_) 740 | { 741 | requestPartiesTrackingRequestsFromCounterParties_.Remove(requestParty); 742 | } 743 | } 744 | } 745 | } 746 | --------------------------------------------------------------------------------