├── Procfile ├── requirements.txt ├── doc-images ├── arch.png ├── esg.png ├── home.png ├── sin.png ├── table.png ├── assets.png ├── improve.png ├── search.png ├── benchmarks.png ├── geography.png └── industry.png ├── static ├── images │ ├── WatsonLogo.png │ ├── search-clear.png │ ├── search-icon.png │ └── spin.svg ├── css │ ├── chart-key.css │ ├── esg-categories.css │ ├── assets.css │ ├── benchmarks.css │ ├── industry.css │ ├── geography.css │ ├── sin.css │ ├── search.css │ ├── table.css │ └── styles.css └── js │ ├── load.js │ ├── table.js │ ├── esg-categories.js │ ├── assets.js │ ├── geography │ ├── geography.js │ ├── d3-tip.js │ ├── world_investment.json │ └── world_investment_default.json │ ├── analysis.js │ ├── sin.js │ ├── industry.js │ ├── search.js │ ├── benchmarks.js │ ├── lib │ └── jquery.autocomplete.min.js │ └── display.js ├── .env.example ├── .gitignore ├── .travis.yml ├── manifest.yml ├── CONTRIBUTING.md ├── MAINTAINERS.md ├── investmentportfolio.py ├── README.md ├── LICENSE ├── run.py └── templates └── index.html /Procfile: -------------------------------------------------------------------------------- 1 | web: python run.py 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | requests 3 | python-dotenv 4 | -------------------------------------------------------------------------------- /doc-images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/arch.png -------------------------------------------------------------------------------- /doc-images/esg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/esg.png -------------------------------------------------------------------------------- /doc-images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/home.png -------------------------------------------------------------------------------- /doc-images/sin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/sin.png -------------------------------------------------------------------------------- /doc-images/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/table.png -------------------------------------------------------------------------------- /doc-images/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/assets.png -------------------------------------------------------------------------------- /doc-images/improve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/improve.png -------------------------------------------------------------------------------- /doc-images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/search.png -------------------------------------------------------------------------------- /doc-images/benchmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/benchmarks.png -------------------------------------------------------------------------------- /doc-images/geography.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/geography.png -------------------------------------------------------------------------------- /doc-images/industry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/doc-images/industry.png -------------------------------------------------------------------------------- /static/images/WatsonLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/static/images/WatsonLogo.png -------------------------------------------------------------------------------- /static/images/search-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/static/images/search-clear.png -------------------------------------------------------------------------------- /static/images/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Analyze-Investment-Portfolio/HEAD/static/images/search-icon.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Investment Portfolio 2 | CRED_PORTFOLIO_USERID_W= 3 | CRED_PORTFOLIO_PWD_W= 4 | CRED_PORTFOLIO_USERID_R= 5 | CRED_PORTFOLIO_PWD_R= 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac files. 2 | **/.DS_Store 3 | .DS_Store 4 | 5 | .git 6 | .env 7 | PortfolioAnalyze_Portfolios 8 | *.pyc 9 | __pycache__ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - pip install -r requirements.txt 6 | script: 7 | - python run.py & sleep 5; if curl http://localhost:8080; then echo “Test passed”; exit 0; else echo “Test failed”; exit 1; fi 8 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | declared-services: 2 | aip-investment-portfolio: 3 | label: fss-portfolio-service 4 | plan: fss-portfolio-service-free-plan 5 | applications: 6 | - path: . 7 | memory: 256M 8 | instances: 1 9 | name: Portfolio-Analyze 10 | disk_quota: 1024M 11 | domain: mybluemix.net 12 | services: 13 | - aip-investment-portfolio 14 | buildpack: python_buildpack 15 | -------------------------------------------------------------------------------- /static/css/chart-key.css: -------------------------------------------------------------------------------- 1 | .key-col { 2 | border-left: 1px solid #bdb9b9; 3 | padding: 20px; 4 | text-align: left; 5 | } 6 | 7 | .key-value { 8 | margin: 0 !important; 9 | } 10 | 11 | .key-dot { 12 | display: inline-block; 13 | height: 10px; 14 | margin-right: .5em; 15 | width: 10px; 16 | } 17 | 18 | .legend { 19 | text-align: left; 20 | } 21 | 22 | .benchmarks-key-col { 23 | border-left: 1px solid #bdb9b9; 24 | padding: 20px; 25 | text-align: left; 26 | } 27 | 28 | .sin-key-col { 29 | border-left: 1px solid #bdb9b9; 30 | padding: 20px; 31 | text-align: left; 32 | } 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#ibmcode on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /static/css/esg-categories.css: -------------------------------------------------------------------------------- 1 | .esg-gauge { 2 | position: relative; 3 | } 4 | 5 | .esg-text { 6 | position: absolute; 7 | top: 50%; 8 | left: 50%; 9 | -webkit-transform: translate(-50%, -50%); 10 | transform: translate(-50%, -50%); 11 | text-align: right; 12 | } 13 | 14 | .esg-amount { 15 | opacity: 0; 16 | font-size: 30px; 17 | font-weight: 300; 18 | -webkit-margin-before: 0em; 19 | -webkit-margin-after: 0em; 20 | } 21 | 22 | .esg-total { 23 | opacity: 0; 24 | font-size: 14px; 25 | -webkit-margin-before: 0em; 26 | -webkit-margin-after: 0em; 27 | } 28 | 29 | .esg-amount, .esg-total { 30 | margin: 0; 31 | } 32 | -------------------------------------------------------------------------------- /static/images/spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /static/js/load.js: -------------------------------------------------------------------------------- 1 | var apiUrl = location.protocol + '//' + location.host + location.pathname + "api/"; 2 | 3 | $(document).ready(function() { 4 | updateText(); 5 | }); 6 | 7 | $('.new-analysis').click(function() { 8 | location.reload(); 9 | }); 10 | 11 | //update interface with portfolios and risk factors 12 | function updateText() { 13 | 14 | //update portfolio lists 15 | var portfolioLists; 16 | $.get(apiUrl + 'look_through_portfolios', function(data) { 17 | 18 | $('.enter-portfolio select').html(function() { 19 | if (data == "No portfolios found.") { 20 | return "Please load a portfolio below."; 21 | } else { 22 | var str = ''; 23 | for (var i = 0; i < data.length; i++) { 24 | str = str + ''; 25 | } 26 | return str; 27 | } 28 | }); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /static/css/assets.css: -------------------------------------------------------------------------------- 1 | .bx--graph-header { 2 | font-weight: 300; 3 | font-size: 24px; 4 | } 5 | 6 | .bx--graph-header select { 7 | font: inherit; 8 | color: blue; 9 | background-color: transparent; 10 | border-radius: 0; 11 | } 12 | 13 | .graph-container { 14 | display: inline-block; 15 | position: relative; 16 | } 17 | 18 | .tooltip { 19 | position: absolute; 20 | top: 50%; 21 | left: 50%; 22 | -webkit-transform: translate(-50%, -50%); 23 | transform: translate(-50%, -50%); 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | pointer-events: none; 28 | width: 100%; 29 | } 30 | 31 | .tooltip p { 32 | text-align: center; 33 | -webkit-margin-before: 0em; 34 | -webkit-margin-after: 0em; 35 | } 36 | 37 | .tooltip .amount { 38 | font-size: 29px; 39 | line-height: 1; 40 | font-weight: 300; 41 | } 42 | 43 | .tooltip .item { 44 | font-weight: 400; 45 | font-size: 14px; 46 | color: #5A6872; 47 | } 48 | -------------------------------------------------------------------------------- /static/css/benchmarks.css: -------------------------------------------------------------------------------- 1 | .line-groupedbar { 2 | stroke-width: 2; 3 | stroke: #FF00FF; 4 | fill: none; 5 | pointer-events: none; 6 | } 7 | 8 | .axis-groupedbar path { 9 | stroke: #5A6872; 10 | } 11 | 12 | .tick-groupedbar line { 13 | stroke: #5A6872; 14 | } 15 | 16 | .tick-groupedbar text { 17 | fill: #5A6872; 18 | } 19 | 20 | .graph-container-groupedbar { 21 | position: relative; 22 | } 23 | 24 | .tooltip-groupedbar { 25 | font-weight: 700; 26 | padding-left: 1rem 2rem; 27 | background-color: #fff; 28 | position: absolute; 29 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); 30 | border: 1px solid #DFE3E6; 31 | padding: .25rem .5rem; 32 | pointer-events: none; 33 | display: none; 34 | } 35 | 36 | .tooltip-groupedbar:after { 37 | content: ''; 38 | top: 100%; 39 | left: 50%; 40 | -webkit-transform: translateX(-50%); 41 | transform: translateX(-50%); 42 | position: absolute; 43 | width: 0; 44 | height: 0; 45 | border-left: 5px solid transparent; 46 | border-right: 5px solid transparent; 47 | border-top: 5px solid #fff; 48 | } 49 | 50 | .y-groupedbar path { 51 | display: none; 52 | } 53 | 54 | .label-groupedbar { 55 | font-size: 10px; 56 | font-weight: 700; 57 | fill: #5A6872; 58 | text-anchor: middle; 59 | } 60 | -------------------------------------------------------------------------------- /static/css/industry.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | fill: #3d70b2; 3 | opacity: .1; 4 | display: none; 5 | pointer-events: none; 6 | } 7 | 8 | .line { 9 | stroke-width: 2; 10 | stroke: #FF00FF; 11 | fill: none; 12 | pointer-events: none; 13 | } 14 | 15 | .axis path { 16 | stroke: #5A6872; 17 | } 18 | 19 | .tick line { 20 | stroke: #5A6872; 21 | } 22 | 23 | .tick text { 24 | fill: #5A6872; 25 | } 26 | 27 | .graph-container-bar { 28 | position: relative; 29 | } 30 | 31 | .tooltip-bar { 32 | font-weight: 700; 33 | padding-left: 1rem 2rem; 34 | background-color: #fff; 35 | position: absolute; 36 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); 37 | border: 1px solid #DFE3E6; 38 | padding: .25rem .5rem; 39 | pointer-events: none; 40 | display: none; 41 | } 42 | 43 | .tooltip-bar:after { 44 | content: ''; 45 | top: 100%; 46 | left: 50%; 47 | -webkit-transform: translateX(-50%); 48 | transform: translateX(-50%); 49 | position: absolute; 50 | width: 0; 51 | height: 0; 52 | border-left: 5px solid transparent; 53 | border-right: 5px solid transparent; 54 | border-top: 5px solid #fff; 55 | } 56 | 57 | .y path { 58 | display: none; 59 | } 60 | 61 | .label { 62 | font-size: 10px; 63 | font-weight: 700; 64 | fill: #5A6872; 65 | text-anchor: middle; 66 | } 67 | 68 | .industry-bar{ 69 | width:1150px; 70 | } 71 | -------------------------------------------------------------------------------- /static/css/geography.css: -------------------------------------------------------------------------------- 1 | .names { 2 | fill: none; 3 | stroke: #fff; 4 | stroke-linejoin: round; 5 | } 6 | 7 | /* tooltip CSS */ 8 | .d3-tip { 9 | line-height: 1.5; 10 | font-weight: 400; 11 | font-family: "avenir next", Arial, sans-serif; 12 | padding: 6px; 13 | background: rgba(0, 0, 0, 0.6); 14 | color: #c0cdf3; 15 | border-radius: 1px; 16 | pointer-events: none; 17 | } 18 | 19 | /* creates a small triangle extender for the tooltip */ 20 | .d3-tip:after { 21 | box-sizing: border-box; 22 | display: inline; 23 | font-size: 8px; 24 | width: 100%; 25 | line-height: 1.5; 26 | color: rgba(0, 0, 0, 0.6); 27 | position: absolute; 28 | pointer-events: none; 29 | } 30 | 31 | 32 | /* northward tooltips */ 33 | .d3-tip.n:after { 34 | content: "\25BC"; 35 | margin: -1px 0 0 0; 36 | top: 100%; 37 | left: 0; 38 | text-align: center; 39 | } 40 | 41 | 42 | /* eastward tooltips */ 43 | .d3-tip.e:after { 44 | content: "\25C0"; 45 | margin: -4px 0 0 0; 46 | top: 50%; 47 | left: -8px; 48 | } 49 | 50 | 51 | /* southward tooltips */ 52 | .d3-tip.s:after { 53 | content: "\25B2"; 54 | margin: 0 0 1px 0; 55 | top: -8px; 56 | left: 0; 57 | text-align: center; 58 | } 59 | 60 | 61 | /* westward tooltips */ 62 | .d3-tip.w:after { 63 | content: "\25B6"; 64 | margin: -4px 0 0 -1px; 65 | top: 50%; 66 | left: 100%; 67 | } 68 | 69 | .details { 70 | color: white; 71 | } 72 | -------------------------------------------------------------------------------- /static/css/sin.css: -------------------------------------------------------------------------------- 1 | .treemap-text { 2 | font-family: "Helvetica Neue", Helvetica, sans-serif; 3 | fill: white; 4 | font-size: 10px; 5 | } 6 | 7 | #sin-treemap { 8 | height: 200px; 9 | width: 400px; 10 | padding-right: 50px; 11 | box-sizing: unset; 12 | } 13 | 14 | #tooltip-treemap { 15 | position: absolute; 16 | height: auto; 17 | padding: 10px; 18 | background-color: #fff; 19 | -webkit-border-radius: 10px; 20 | -moz-border-radius: 10px; 21 | border-radius: 10px; 22 | -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 23 | -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 24 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 25 | pointer-events: none; 26 | } 27 | 28 | #tooltip-treemap.hidden { 29 | display: none; 30 | } 31 | 32 | #tooltip-treemap p { 33 | margin: 0; 34 | font-family: sans-serif; 35 | font-size: 16px; 36 | line-height: 20px; 37 | } 38 | 39 | .sin-exposure { 40 | margin-left: 5%; 41 | margin-right: 5%; 42 | border: thin solid #dcdbdb; 43 | width: 315px; 44 | align-self: center; 45 | } 46 | 47 | .sin-breakdown { 48 | border: thin solid #dcdbdb; 49 | width: 700px; 50 | margin-right: 30%; 51 | } 52 | 53 | .sin-body { 54 | display: -webkit-box; 55 | } 56 | 57 | .sin-exposure-title { 58 | margin-bottom: 0px; 59 | text-align: left; 60 | padding-left: 30px; 61 | } 62 | 63 | .sin-breakdown-title { 64 | text-align: left; 65 | padding-left: 30px; 66 | } 67 | -------------------------------------------------------------------------------- /static/js/table.js: -------------------------------------------------------------------------------- 1 | function compositionTable() { 2 | 3 | //use global variable declared in analysis.js 4 | var portfolio = compositionData; 5 | 6 | $('#results > tr').remove(); 7 | 8 | if(portfolio.length>0){ 9 | var rowData=''; 10 | var tableCols=['name','value ($USD)','Portfolio Contribution (%)','Industry Sector','Asset Class','Geography']; //Hardcoded to GUI order. Otherwise would iterate through key,value. 11 | $.each(tableCols, function(c) { 12 | if(capitalize(tableCols[c]).trim()!="Asset"){ 13 | rowData+=''+capitalize(tableCols[c])+'' 14 | } 15 | }); 16 | $("#results > thead").append(rowData+""); 17 | for (var i = 0; i < portfolio.length; i++) { 18 | 19 | rowData=''; 20 | for(var j=0;j"; 27 | } 28 | $("#results > tbody").append(rowData+""); 29 | 30 | } 31 | 32 | //source datatable: https://datatables.net/examples/basic_init/multi_col_sort.html 33 | $('#results').DataTable( { 34 | columnDefs: [ { 35 | targets: [ 0 ], 36 | orderData: [ 0, 1 ] 37 | }, { 38 | targets: [ 1 ], 39 | orderData: [ 1, 0 ] 40 | }, { 41 | targets: [ 4 ], 42 | orderData: [ 4, 0 ] 43 | } ] 44 | } ); 45 | } 46 | 47 | function capitalize(s) { 48 | return s[0].toUpperCase() + s.substr(1); 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /static/js/esg-categories.js: -------------------------------------------------------------------------------- 1 | function esgGaugeCharts() { 2 | 3 | //use global variable declared in analysis.js 4 | var portfolioEsg = esgData[esgPortfolio]; 5 | 6 | esgChart(portfolioEsg.esg_Sustainability, 10, "sustainability", '#3b1a40'); 7 | esgChart(portfolioEsg.esg_Controversy, 10, "controversy", '#473793'); 8 | esgChart(portfolioEsg.esg_Environmental, 10, "environmental", '#3c6df0'); 9 | esgChart(portfolioEsg.esg_Social, 10, "social", '#00a68f'); 10 | esgChart(portfolioEsg.esg_Governance, 10, "governance", '#56D2BB'); 11 | 12 | } 13 | 14 | function esgChart(amount, total, esgType, color) { 15 | 16 | //esg gauge chart 17 | //source gauge chart: http://www.carbondesignsystem.com/data-vis/gauge/code 18 | var tau = 2 * Math.PI; 19 | var radius = 80; 20 | var padding = 30; 21 | var boxSize = (radius + padding) * 2; 22 | var ratio = amount / total; 23 | 24 | var arc = d3.arc().innerRadius(radius).outerRadius(radius - 10).startAngle(0); 25 | 26 | var svg = d3.select('#esg-' + esgType + '-chart').attr('width', boxSize).attr('height', boxSize); 27 | 28 | var g = svg.append('g').attr('transform', 'translate(' + boxSize / 2 + ', ' + boxSize / 2 + ')'); 29 | 30 | //background Arc 31 | var background = g.append('path').datum({ 32 | endAngle: tau 33 | }).style('fill', '#dfe3e6').attr('d', arc); 34 | 35 | //foreground Arc 36 | var foreground = g.append('path').datum({ 37 | endAngle: 0 38 | }).style('fill', color).transition().duration(1000).delay(1000).attrTween('d', arcTween(ratio * tau)); 39 | 40 | //text Labels 41 | var amountText = d3.select('.esg-' + esgType + '-amount'); 42 | amountText.style('opacity', 0).transition().duration(1000).delay(1500).style('opacity', 1).text(amount.toFixed(2)); 43 | const totalText = d3.select('.esg-' + esgType + '-total'); 44 | totalText 45 | .style('opacity', 0) 46 | .transition() 47 | .duration(1000) 48 | .delay(1700) 49 | .style('opacity', 1) 50 | .text(`/${10}`); 51 | 52 | //animation function 53 | function arcTween(newAngle) { 54 | return function(d) { 55 | var interpolate = d3.interpolate(d.endAngle, newAngle); 56 | return function(t) { 57 | d.endAngle = interpolate(t); 58 | 59 | return arc(d); 60 | }; 61 | }; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /static/js/assets.js: -------------------------------------------------------------------------------- 1 | function assetAllocationChartData() { 2 | 3 | //use global variable declared in analysis.js 4 | var portfolioData = assetData; 5 | 6 | var pieData = []; 7 | var count = 0; 8 | 9 | for (var key in portfolioData) { 10 | var entry = [key, portfolioData[key] / NAV] 11 | pieData.push(entry); 12 | } 13 | 14 | return pieData 15 | } 16 | 17 | 18 | function assetAllocationChart() { 19 | 20 | var data = assetAllocationChartData(); 21 | 22 | //assets pie chart 23 | //source pie chart: http://www.carbondesignsystem.com/data-vis/pie-chart/code 24 | var radius = 96; 25 | var width = radius * 2; 26 | var height = radius * 2; 27 | 28 | var svg = d3.select('#allocation-chart').attr('width', width).attr('height', height).append('g').attr('class', 'group-container').attr('transform', 'translate(' + width / 2 + ', ' + height / 2 + ')'); 29 | 30 | var colors = ['#0C35AA','#1D6CF7',"#71A8FC","#CADFFE","#4F2794", "#6E3AC6","#A875FC","#D0B2FD","#054548","#179E99","#31D5D1","#DBFBFB"] 31 | var color = d3.scaleOrdinal(colors); 32 | 33 | var pie = d3.pie().sort(null).value(function(d) { 34 | return d[1]; 35 | }); 36 | 37 | var path = d3.arc().outerRadius(radius - 10).innerRadius(radius - 40); 38 | 39 | var pathTwo = d3.arc().outerRadius(radius).innerRadius(radius - 40); 40 | 41 | var arc = svg.selectAll('.arc').data(pie(data)).enter().append('g').attr('class', 'arc'); 42 | 43 | arc.append('path').attr('d', path).attr('fill', function(d, i) { 44 | return color(i); 45 | }).attr('stroke-width', 2).attr('stroke', '#FFFFFF').on('mouseover', function(d) { 46 | d3.select(this).transition().style('cursor', 'pointer').attr('d', pathTwo); 47 | 48 | var tooltip = d3.select('.tooltip').style('display', 'inherit'); 49 | var amount = d3.select('.amount') 50 | var item = d3.select('.item'); 51 | 52 | amount.text('' + Number(d.data[1] * 100).toFixed(2) + '%'); 53 | 54 | item.text('' + d.data[0]); 55 | }).on('mouseout', function(d) { 56 | var tooltip = d3.select('.tooltip').style('display', 'none'); 57 | 58 | d3.select(this).transition().attr('d', path); 59 | }); 60 | 61 | // add key 62 | $('.key').html(function() { 63 | var str = '' 64 | for (var i = 0; i < data.length; i++) { 65 | str = str + '

' + data[i][0] + '

'; 66 | } 67 | return str; 68 | }); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /static/css/search.css: -------------------------------------------------------------------------------- 1 | .autocomplete-suggestions { 2 | border: 1px solid #999; 3 | background: #fff; 4 | cursor: default; 5 | overflow: auto; 6 | } 7 | 8 | .autocomplete-suggestion { 9 | padding: 5px 15px; 10 | font-size: 1em; 11 | white-space: nowrap; 12 | overflow: hidden; 13 | } 14 | 15 | .autocomplete-selected { 16 | background: #f0f0f0; 17 | } 18 | 19 | .autocomplete-suggestions strong { 20 | font-weight: normal; 21 | color: #3399ff; 22 | } 23 | 24 | #searchfield { 25 | display: block; 26 | width: 100%; 27 | text-align: center; 28 | margin-bottom: 35px; 29 | } 30 | 31 | #searchfield form { 32 | display: inline-block; 33 | background: #f4f7fb; 34 | padding: 0; 35 | margin: 0; 36 | padding: 5px; 37 | border-radius: 3px; 38 | margin: 5px 0 0 0; 39 | } 40 | 41 | #searchfield form .biginput { 42 | width: 600px; 43 | height: 40px; 44 | padding: 0 10px 0 10px; 45 | background-color: #f4f7fb; 46 | border: 1px solid #155382; 47 | border-radius: 3px; 48 | font-weight: bold; 49 | font-size: 1em; 50 | -webkit-transition: all 0.2s linear; 51 | -moz-transition: all 0.2s linear; 52 | transition: all 0.2s linear; 53 | background: url(../images/search-icon.png) no-repeat scroll; 54 | padding-left: 50px; 55 | } 56 | 57 | #searchfield form .biginput:focus { 58 | /* color: #858585; */ 59 | } 60 | 61 | #outputbox { 62 | display: none; 63 | } 64 | 65 | .graph-container-portfolio-bar { 66 | position: relative; 67 | } 68 | 69 | .price-color { 70 | display: inline; 71 | color: blue; 72 | } 73 | 74 | .direct-color { 75 | display: inline; 76 | font-size: 1.4rem; 77 | color: blue; 78 | } 79 | 80 | .indirect-color { 81 | display: inline; 82 | font-size: 1.4rem; 83 | color: purple; 84 | } 85 | 86 | .search-result-table { 87 | table-layout: fixed; 88 | } 89 | 90 | .search-result-table td { 91 | overflow: hidden; 92 | width: 400px; 93 | } 94 | 95 | .esg-score { 96 | margin-left: 30px; 97 | border: thin solid #dcdbdb; 98 | width: 400px; 99 | margin-bottom: 150px; 100 | } 101 | 102 | .portfolio-name { 103 | border: thin solid #dcdbdb; 104 | width: 400px; 105 | margin-bottom: 150px; 106 | } 107 | 108 | .additional-exposure { 109 | margin-right: 30px; 110 | border: thin solid #dcdbdb; 111 | width: 400px; 112 | margin-bottom: 150px; 113 | } 114 | 115 | #outputbox { 116 | display: flex; 117 | align-items: stretch; 118 | justify-content: space-between; 119 | } 120 | 121 | 122 | /* 123 | #outputbox div { 124 | display: table-cell; 125 | text-align: center; 126 | border: thin solid #999; 127 | }*/ 128 | -------------------------------------------------------------------------------- /static/js/geography/geography.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function geographyChart() { 4 | 5 | var portfolioData = geographyData; 6 | 7 | var format = d3.format(","); 8 | 9 | // Set tooltips 10 | var tip = d3.tip() 11 | .attr('class', 'd3-tip') 12 | //.offset([-10, 0]) 13 | .direction('e') 14 | .html(function(d) { 15 | return "Country: " + d.properties.name + "
" + "Investments: " + format(Number(d.investments).toFixed(2)) +""; 16 | }) 17 | 18 | var margin = {top: 0, right: 0, bottom: 0, left: 0}, 19 | width = 576 - margin.left - margin.right, 20 | height = 300 - margin.top - margin.bottom; 21 | 22 | 23 | var color = d3.scaleThreshold() 24 | .domain([0,1,5,10,50,100,500,1000]) 25 | .range(["rgb(200,200,200)", "rgb(200,200,200)", "rgb(107,174,214)", "rgb(66,146,198)","rgb(33,113,181)","rgb(8,81,156)","rgb(8,48,107)","rgb(3,19,43)"]); 26 | 27 | 28 | var path = d3.geoPath(); 29 | 30 | var svg = d3.select('#geography-chart') 31 | .attr("width", width) 32 | .attr("height", height) 33 | .append('g') 34 | .attr('class', 'map'); 35 | 36 | var projection = d3.geoMercator() 37 | //.scale(130) 38 | .scale(90) 39 | .translate( [width / 2, height / 1.5]); 40 | 41 | var path = d3.geoPath().projection(projection); 42 | 43 | svg.call(tip); 44 | 45 | queue() 46 | .defer(d3.json, "/static/js/geography/world_countries.json") 47 | .defer(d3.json, "/static/js/geography/world_investment.json") 48 | .await(ready); 49 | 50 | function ready(error, data, investments) { 51 | var investmentsById = {}; 52 | 53 | investments.forEach(function(d) { investmentsById[d.id] = +d.investments; }); 54 | data.features.forEach(function(d) { d.investments = investmentsById[d.id] }); 55 | 56 | svg.append("g") 57 | .attr("class", "countries") 58 | .selectAll("path") 59 | .data(data.features) 60 | .enter().append("path") 61 | .attr("d", path) 62 | .style("fill", function(d) { return color(investmentsById[d.id]); }) 63 | .style('stroke', 'white') 64 | .style('stroke-width', 1.5) 65 | .style("opacity",0.8) 66 | // tooltips 67 | .style("stroke","white") 68 | .style('stroke-width', 0.3) 69 | .on('mouseover',function(d){ 70 | tip.show(d); 71 | 72 | d3.select(this) 73 | .style("opacity", 1) 74 | .style("stroke","white") 75 | .style("stroke-width",3); 76 | }) 77 | .on('mouseout', function(d){ 78 | tip.hide(d); 79 | 80 | d3.select(this) 81 | .style("opacity", 0.8) 82 | .style("stroke","white") 83 | .style("stroke-width",0.3); 84 | }); 85 | 86 | svg.append("path") 87 | .datum(topojson.mesh(data.features, function(a, b) { return a.id !== b.id; })) 88 | // .datum(topojson.mesh(data.features, function(a, b) { return a !== b; })) 89 | .attr("class", "names") 90 | .attr("d", path); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers Guide 2 | 3 | This guide is intended for maintainers — anybody with commit access to one or 4 | more Developer Journey repositories. 5 | 6 | ## Methodology: 7 | 8 | A master branch. This branch MUST be releasable at all times. Commits and 9 | merges against this branch MUST contain only bugfixes and/or security fixes. 10 | Maintenance releases are tagged against master. 11 | 12 | A develop branch. This branch contains your proposed changes. 13 | 14 | The remainder of this document details how to merge pull requests to the 15 | repositories. 16 | 17 | ## Merge approval 18 | 19 | The project maintainers use LGTM (Looks Good To Me) in comments on the code 20 | review to indicate acceptance. A change requires LGTMs from two of the members 21 | of the [eti-journey-admins](https://github.com/orgs/IBM/teams/eti-journey-admins/members) 22 | team. If the code is written by a member, the change only requires one more 23 | LGTM. 24 | 25 | ## Reviewing Pull Requests 26 | 27 | We recommend reviewing pull requests directly within GitHub. This allows a 28 | public commentary on changes, providing transparency for all users. When 29 | providing feedback be civil, courteous, and kind. Disagreement is fine, so 30 | long as the discourse is carried out politely. If we see a record of uncivil 31 | or abusive comments, we will revoke your commit privileges and invite you to 32 | leave the project. 33 | 34 | During your review, consider the following points: 35 | 36 | ### Does the change have impact? 37 | 38 | While fixing typos is nice as it adds to the overall quality of the project, 39 | merging a typo fix at a time can be a waste of effort. 40 | (Merging many typo fixes because somebody reviewed the entire component, 41 | however, is useful!) Other examples to be wary of: 42 | 43 | Changes in variable names. Ask whether or not the change will make 44 | understanding the code easier, or if it could simply a personal preference 45 | on the part of the author. 46 | 47 | Essentially: feel free to close issues that do not have impact. 48 | 49 | ### Do the changes make sense? 50 | 51 | If you do not understand what the changes are or what they accomplish, 52 | ask the author for clarification. Ask the author to add comments and/or 53 | clarify test case names to make the intentions clear. 54 | 55 | At times, such clarification will reveal that the author may not be using 56 | the code correctly, or is unaware of features that accommodate their needs. 57 | If you feel this is the case, work up a code sample that would address the 58 | issue for them, and feel free to close the issue once they confirm. 59 | 60 | ### Is this a new feature? If so: 61 | 62 | Does the issue contain narrative indicating the need for the feature? If not, 63 | ask them to provide that information. Since the issue will be linked in the 64 | changelog, this will often be a user's first introduction to it. 65 | 66 | Are new unit tests in place that test all new behaviors introduced? If not, do 67 | not merge the feature until they are! 68 | Is documentation in place for the new feature? (See the documentation 69 | guidelines). If not do not merge the feature until it is! 70 | Is the feature necessary for general use cases? Try and keep the scope of any 71 | given component narrow. If a proposed feature does not fit that scope, 72 | recommend to the user that they maintain the feature on their own, and close 73 | the request. You may also recommend that they see if the feature gains traction 74 | amongst other users, and suggest they re-submit when they can show such support. 75 | -------------------------------------------------------------------------------- /static/js/analysis.js: -------------------------------------------------------------------------------- 1 | var apiUrl = location.protocol + '//' + location.host + location.pathname + "api/"; 2 | 3 | //declare global variables 4 | var NAV = 0; 5 | var assetData = {}; 6 | var sectorData = {}; 7 | var geographyData = {}; 8 | var compositionTable = {}; 9 | var sinData = {}; 10 | var searchData = {}; 11 | var searchPortfolio = ""; 12 | 13 | var esgData = {}; 14 | var esgPortfolio = ""; 15 | 16 | //check user input and process, generate result in tables 17 | $('.run-analysis').click(function() { 18 | var portfolioSelected = $('.enter-portfolio select').find(":selected").text(); 19 | var Portfolio = JSON.stringify(portfolioSelected); 20 | 21 | if (Portfolio.includes('Loading...')) { 22 | alert("Load a portfolio first using Investment Portfolio service"); 23 | return; 24 | } else if (Portfolio.includes('[pick portfolio]')) { 25 | alert("Select a portfolio"); 26 | return; 27 | } 28 | 29 | document.getElementById('loader').style.display = "flex"; 30 | 31 | $.ajax({ 32 | type: 'GET', 33 | url: apiUrl + 'portfolio-analyze/' + String(Portfolio.replace(/"/g, "")), 34 | dataType: 'json', 35 | contentType: 'application/json', 36 | success: function(data, input_parameters) { 37 | console.log(data); 38 | 39 | document.getElementById('analysis-input').style.display = "none"; 40 | document.getElementById('started-button').style.display = "none"; 41 | document.getElementById('loader').style.display = "none"; 42 | document.getElementById('analysis').style.display = "block"; 43 | 44 | portfolioName = String(Portfolio.replace(/"/g, "")); 45 | 46 | //capture esg data 47 | NAV = data.NAV; 48 | assetData = data.composition["Asset Class"]; 49 | sectorData = data.composition.sector; 50 | geographyData = data.composition.geography; 51 | compositionData = data.portfolio; 52 | sinData = data.sin; 53 | searchData = data.search; 54 | searchPortfolio = portfolioName; 55 | 56 | esgData = data.esg; 57 | esgPortfolio = portfolioSelected; 58 | 59 | assetAllocationChart(); 60 | compositionTable(); 61 | 62 | }, 63 | error: function(jqXHR, textStatus, errorThrown) { 64 | //reload on error 65 | document.getElementById('loader').style.display = "none"; 66 | 67 | alert("Error: Try again") 68 | console.log(errorThrown); 69 | console.log(textStatus); 70 | console.log(jqXHR); 71 | } 72 | }); 73 | }); 74 | 75 | 76 | $("#portfolio_file").change(function(e) { 77 | // The event listener for the file upload 78 | var ext = $("input#portfolio_file").val().split(".").pop().toLowerCase(); 79 | 80 | document.getElementById('loader').style.display = "flex"; 81 | if ($.inArray(ext, ["csv"]) == -1) { 82 | alert('Upload CSV'); 83 | return false; 84 | } 85 | if (e.target.files != undefined) { 86 | var reader = new FileReader(); 87 | reader.onload = function(e) { 88 | var csvval = e.target.result.split("\n"); 89 | var json_file = JSON.stringify(csvval); 90 | $.ajax({ 91 | type: 'POST', 92 | url: apiUrl + 'upload', 93 | data: json_file, 94 | dataType: 'json', 95 | contentType: 'application/json', 96 | success: function(data) { 97 | console.log(data); 98 | alert("Portfolio uploaded successfully."); 99 | window.location = window.location; 100 | } 101 | }); 102 | }; 103 | reader.readAsText(e.target.files.item(0)); 104 | } 105 | return false; 106 | }); 107 | -------------------------------------------------------------------------------- /static/css/table.css: -------------------------------------------------------------------------------- 1 | #result { 2 | width: 1050px; 3 | } 4 | 5 | .bx--responsive-table--static-size { 6 | width: 1050px; 7 | } 8 | 9 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 10 | color: #333; 11 | } 12 | 13 | .dataTables_wrapper .dataTables_paginate { 14 | float: center; 15 | text-align: center; 16 | padding-top: 0.25em; 17 | } 18 | 19 | .dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { 20 | cursor: default; 21 | color: #666 !important; 22 | border: 1px solid transparent; 23 | background: transparent; 24 | box-shadow: none; 25 | } 26 | 27 | .dataTables_wrapper .dataTables_paginate .paginate_button { 28 | box-sizing: border-box; 29 | display: inline-block; 30 | min-width: 1.5em; 31 | padding: 0.5em 1em; 32 | margin-left: 2px; 33 | text-align: center; 34 | text-decoration: none !important; 35 | cursor: pointer; 36 | *cursor: hand; 37 | color: #333 !important; 38 | border: 1px solid transparent; 39 | border-radius: 2px; 40 | } 41 | 42 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 43 | color: #333; 44 | } 45 | 46 | .dataTables_wrapper .dataTables_paginate { 47 | text-align: center; 48 | } 49 | 50 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 51 | color: #333; 52 | } 53 | 54 | .dataTables_wrapper .dataTables_paginate { 55 | text-align: center; 56 | } 57 | 58 | .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { 59 | color: #333 !important; 60 | border: 1px solid #979797; 61 | background-color: white; 62 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc)); 63 | background: -webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%); 64 | background: -moz-linear-gradient(top, #fff 0%, #dcdcdc 100%); 65 | background: -ms-linear-gradient(top, #fff 0%, #dcdcdc 100%); 66 | background: -o-linear-gradient(top, #fff 0%, #dcdcdc 100%); 67 | background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%); 68 | } 69 | 70 | .dataTables_wrapper .dataTables_paginate .paginate_button { 71 | box-sizing: border-box; 72 | display: inline-block; 73 | min-width: 1.5em; 74 | padding: 0.5em 1em; 75 | margin-left: 2px; 76 | text-align: center; 77 | text-decoration: none !important; 78 | cursor: pointer; 79 | *cursor: hand; 80 | color: #333 !important; 81 | border: 1px solid transparent; 82 | border-radius: 2px; 83 | } 84 | 85 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 86 | color: #333; 87 | } 88 | 89 | .dataTables_wrapper .dataTables_paginate { 90 | text-align: center; 91 | } 92 | 93 | .dataTables_wrapper .dataTables_paginate .paginate_button { 94 | box-sizing: border-box; 95 | display: inline-block; 96 | min-width: 1.5em; 97 | padding: 0.5em 1em; 98 | margin-left: 2px; 99 | text-align: center; 100 | text-decoration: none !important; 101 | cursor: pointer; 102 | *cursor: hand; 103 | color: #333 !important; 104 | border: 1px solid transparent; 105 | border-radius: 2px; 106 | } 107 | 108 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 109 | color: #333; 110 | } 111 | 112 | .dataTables_wrapper .dataTables_paginate { 113 | text-align: center; 114 | } 115 | 116 | .odd { 117 | background-color: #f4f7fb; 118 | } 119 | -------------------------------------------------------------------------------- /static/js/sin.js: -------------------------------------------------------------------------------- 1 | function sinCharts() { 2 | 3 | sinGaugeChart(); 4 | var getTreeMapData = sinTreeMapData(); 5 | sinTreeMap(getTreeMapData); 6 | 7 | } 8 | 9 | 10 | function sinTreeMapData() { 11 | 12 | var data = { 13 | "name": "SinInvestments", 14 | "children": [{ 15 | "name": "Alcohol", 16 | "value": sinData.has_Alcohol 17 | }, 18 | { 19 | "name": "Fossil Fuels", 20 | "value": sinData["has_Fossil Fuels"] 21 | }, 22 | { 23 | "name": "Gambling", 24 | "value": sinData.has_Gambling 25 | }, 26 | { 27 | "name": "Military", 28 | "value": sinData.has_Military 29 | }, 30 | { 31 | "name": "Tobacco", 32 | "value": sinData.has_Tobacco 33 | } 34 | ] 35 | }; 36 | 37 | return data; 38 | 39 | } 40 | 41 | 42 | function sinTreeMap(sinTreeMapData) { 43 | 44 | var data = sinTreeMapData; 45 | 46 | var colors = ['#3b1a40', '#473793', '#3c6df0', '#00a68f', '#56D2BB'] 47 | var color = d3.scaleOrdinal(colors); 48 | var keys = ['Alcohol', 'Fossil Fuels', 'Gambling', 'Military', 'Tobacco'] 49 | 50 | //treemap tooltip source: http://bl.ocks.org/tgk/6044254 51 | var mousemove = function(d) { 52 | var xPosition = d3.event.pageX + 5; 53 | var yPosition = d3.event.pageY + 5; 54 | 55 | d3.select("#tooltip-treemap") 56 | .style("left", xPosition + "px") 57 | .style("top", yPosition + "px"); 58 | d3.select("#tooltip-treemap #heading-treemap") 59 | .text(d["data"]["name"]); 60 | d3.select("#tooltip-treemap #percentage-treemap") 61 | .text(Number((d["value"] / NAV) * 100).toFixed(2) + "%"); 62 | d3.select("#tooltip-treemap").classed("hidden", false); 63 | }; 64 | 65 | var mouseout = function() { 66 | d3.select("#tooltip-treemap").classed("hidden", true); 67 | }; 68 | 69 | 70 | var treemapLayout = d3.treemap() 71 | .size([400, 200]) 72 | .paddingTop(10) 73 | .paddingInner(1); 74 | 75 | var rootNode = d3.hierarchy(data) 76 | 77 | rootNode.sum(function(d) { 78 | return d.value; 79 | }); 80 | 81 | treemapLayout(rootNode); 82 | 83 | var nodes = d3.select('#sin-treemap') 84 | .selectAll('g') 85 | .data(rootNode.leaves()) 86 | .enter() 87 | .append('g') 88 | .attr('transform', function(d) { 89 | return 'translate(' + [d.x0, d.y0] + ')' 90 | }) 91 | .on("mousemove", mousemove) 92 | .on("mouseout", mouseout); 93 | 94 | 95 | nodes 96 | .append('rect') 97 | .attr('class', 'treemap-rect') 98 | .attr('width', function(d) { 99 | return d.x1 - d.x0; 100 | }) 101 | .attr('height', function(d) { 102 | return d.y1 - d.y0; 103 | }) 104 | .attr('fill', function(d, i) { 105 | return color(i); 106 | }) 107 | .on('mouseover', function(d) { 108 | d3.select(this).transition().style('cursor', 'pointer'); 109 | }).on('mouseout', function(d) { 110 | //remove the info text on mouse out. 111 | d3.select(this).select('text').remove(); 112 | }); 113 | 114 | 115 | //add key 116 | $('.sin-treemap-key').html(function() { 117 | var str = '' 118 | for (var i = 0; i < keys.length; i++) { 119 | str = str + '

' + keys[i] + '

'; 120 | } 121 | return str; 122 | }); 123 | 124 | } 125 | 126 | 127 | function sinGaugeChart() { 128 | 129 | //source gauge chart: http://www.carbondesignsystem.com/data-vis/gauge/code 130 | var tau = 2 * Math.PI; 131 | var radius = 65; 132 | var padding = 30; 133 | var boxSize = (radius + padding) * 2; 134 | 135 | var ratio = (sinData.has_Alcohol + sinData["has_Fossil Fuels"] + sinData.has_Gambling + sinData.has_Military + sinData.has_Tobacco) / NAV; 136 | var percent = Math.round(ratio * 100); 137 | 138 | var arc = d3.arc().innerRadius(radius).outerRadius(radius - 15).startAngle(0); 139 | 140 | var svg = d3.select('#sin-chart').attr('width', boxSize).attr('height', boxSize); 141 | 142 | var g = svg.append('g').attr('transform', 'translate(' + boxSize / 2 + ', ' + boxSize / 2 + ')'); 143 | 144 | //background Arc 145 | var background = g.append('path').datum({ 146 | endAngle: tau 147 | }).style('fill', '#dfe3e6').attr('d', arc); 148 | 149 | //foreground Arc 150 | var foreground = g.append('path').datum({ 151 | endAngle: 0 152 | }).style('fill', '#473793').transition().duration(500).delay(100).attrTween('d', arcTween(ratio * tau)); 153 | 154 | //animation function 155 | function arcTween(newAngle) { 156 | return function(d) { 157 | var interpolate = d3.interpolate(d.endAngle, newAngle); 158 | return function(t) { 159 | d.endAngle = interpolate(t); 160 | 161 | return arc(d); 162 | }; 163 | }; 164 | } 165 | 166 | $('.sin-text').html(function() { 167 | return '
Your portfolio is comprised of
' + percent + '%
"sin" investments".
'; 168 | }); 169 | 170 | 171 | } 172 | -------------------------------------------------------------------------------- /static/js/industry.js: -------------------------------------------------------------------------------- 1 | function industryChartData() { 2 | 3 | //use global variable declared in analysis.js 4 | var portfolioData = sectorData; 5 | 6 | var chartData = []; 7 | var industryData = []; 8 | var other = 0; 9 | var other_pct = 0; 10 | var total = 0; 11 | 12 | for (var key in portfolioData) { 13 | var obj = { 14 | value: portfolioData[key], 15 | industry: key 16 | } 17 | chartData.push(obj); 18 | } 19 | 20 | for (var j = 0; j < chartData.length; j++) { 21 | pct = chartData[j]["value"] / NAV; 22 | if (pct < .01) { 23 | other += chartData[j]["value"]; 24 | other_pct += pct; 25 | } else { 26 | industryData.push({ 27 | value: chartData[j]["value"], 28 | industry: chartData[j]["industry"], 29 | percent: pct 30 | }) 31 | } 32 | } 33 | industryData.push({ 34 | value: other, 35 | industry: "Other", 36 | percent: other_pct 37 | }) 38 | 39 | return industryData 40 | } 41 | 42 | function industryChart() { 43 | 44 | var data = industryChartData(); 45 | 46 | //create industry bar chart 47 | //source bar graph: http://www.carbondesignsystem.com/data-vis/bar-graph/code 48 | var margin = { 49 | top: 30, 50 | right: 20, 51 | bottom: 60, 52 | left: 65 53 | }; 54 | var width = 1200 - (margin.left + margin.right); 55 | var height = 300 - (margin.top + margin.bottom); 56 | var labelOffset = 50; 57 | var axisOffset = 16; 58 | 59 | var formatPercent = d3.format(".0%"); 60 | var formatPercent_four = d3.format(",.4%"); 61 | 62 | //set the scales 63 | var x = d3.scaleBand().rangeRound([0, width]).domain(data.map(function(d) { 64 | return d.industry; 65 | })).padding(0.5); 66 | 67 | var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, function(d) { 68 | return d.percent; 69 | })]); 70 | 71 | //set the axes 72 | var xAxis = d3.axisBottom().scale(x).tickSize(0); 73 | var yAxis = d3.axisLeft().ticks(4).tickSize(-width).scale(y.nice()).tickFormat(formatPercent); 74 | 75 | //set up SVG with initial transform to avoid repeat positioning 76 | var svg = d3.select('#industry-chart').attr('class', 'graph').attr('width', width + (margin.left + margin.right)).attr('height', height + (margin.top + margin.bottom)).append('g').attr('class', 'group-container').attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')').attr('font-family', 'ibm-plex-sans'); 77 | 78 | //add Y axis 79 | svg.append('g') 80 | .attr('class', 'axis y') 81 | .attr('stroke-dasharray', '4') 82 | .call(yAxis) 83 | .selectAll('text') 84 | .attr("x", -axisOffset) 85 | .attr('font-family', 'ibm-plex-sans'); 86 | 87 | //add X axis 88 | svg.append('g') 89 | .attr('class', 'axis x') 90 | .attr('transform', 'translate(0, ' + height + ')') 91 | .call(xAxis) 92 | .selectAll('text') 93 | .call(wrap, 60); 94 | 95 | svg.append('g') 96 | .attr('class', 'bar-container') 97 | .selectAll('rect') 98 | .data(data) 99 | .enter() 100 | .append('rect') 101 | .attr('class', 'bar') 102 | .attr('x', function(d) { 103 | return x(d.industry); 104 | }) 105 | .attr('y', function(d) { 106 | return height; 107 | }) 108 | .attr('height', 0) 109 | .attr('width', x.bandwidth()) 110 | .attr('fill', '#93C4FB') 111 | .transition() 112 | .duration(500) 113 | .delay((d, i) => i * 50) 114 | .attr('height', (d) => height - y(d.percent)) 115 | .attr('y', (d) => y(d.percent)); 116 | 117 | //select Tooltip 118 | var tooltip = d3.select('.tooltip-bar'); 119 | 120 | var bars = svg.selectAll('.bar').on('mouseover', function(d) { 121 | var color = d3.color('#93C4FB').darker(); 122 | d3.select(this).attr('fill', color); 123 | tooltip.style('display', 'inherit').text(Number(d.percent * 100).toFixed(2) + '%').style('top', y(d.percent) - axisOffset + 'px'); 124 | 125 | var bandwidth = x.bandwidth(); 126 | var tooltipWidth = tooltip.nodes()[0].getBoundingClientRect().width; 127 | var offset = (tooltipWidth - bandwidth) / 2; 128 | 129 | tooltip.style('left', x(d.industry) + margin.left - offset + 'px'); 130 | }).on('mouseout', function(d) { 131 | d3.select(this).transition().duration(250).attr('fill', '#93C4FB'); 132 | tooltip.style('display', 'none'); 133 | }); 134 | 135 | function wrap(text, width) { 136 | text.each(function() { 137 | var text = d3.select(this), 138 | words = text.text().split(/\s+/).reverse(), 139 | word, 140 | line = [], 141 | lineNumber = 0, 142 | lineHeight = 1.1, // ems 143 | y = text.attr("y"), 144 | dy = parseFloat(text.attr("dy")) + 1, 145 | tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em"); 146 | while (word = words.pop()) { 147 | line.push(word); 148 | tspan.text(line.join(" ")); 149 | if (tspan.node().getComputedTextLength() > width) { 150 | line.pop(); 151 | tspan.text(line.join(" ")); 152 | line = [word]; 153 | tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); 154 | } 155 | } 156 | }); 157 | } 158 | 159 | 160 | } 161 | -------------------------------------------------------------------------------- /static/js/search.js: -------------------------------------------------------------------------------- 1 | 2 | function clearSearch() { 3 | document.getElementById('autocomplete').value = ''; 4 | document.getElementById('search-description').style.display = "block"; 5 | document.getElementById('outputbox').style.display = "none"; 6 | } 7 | 8 | 9 | function searchField() { 10 | 11 | var portfolio = searchData; 12 | var portfolioName = searchPortfolio; 13 | var data = []; 14 | 15 | for(var i=0; i'; 49 | $('#portfolioName').html(portfolioTitle); 50 | 51 | var portfolioPrice = '$' + Number(data.indirect + data.direct).toFixed(2) + ''; 52 | $('#portfolioPrice').html(portfolioPrice); 53 | 54 | total_exposure = Number(((data.direct + data.indirect) / data.NAV) * 100).toFixed(2); 55 | 56 | var portfolioDirect = 'Your portfolio is comprised of
' + total_exposure + '%
of this instrument'; 57 | $('#portfolioDirect').html(portfolioDirect); 58 | 59 | indirectPart = Number((data.indirect / data.NAV) * 100).toFixed(2); 60 | var portfolioIndirect = 'Did you know that
' + indirectPart + '%
of this
coverage is owned in either ETF’s
or Mutual Funds in your portfolio?'; 61 | $('#portfolioIndirect').html(portfolioIndirect); 62 | 63 | var portfolioEsgData = [ 64 | {esg:"Sustainability",value:data.esg.esg_Controversy}, 65 | {esg:"Controversy",value:data.esg.esg_Environmental}, 66 | {esg:"Environmental",value:data.esg.esg_Governance}, 67 | {esg:"Social",value:data.esg.esg_Social}, 68 | {esg:"Governance",value:data.esg.esg_Sustainability} 69 | ] 70 | 71 | portfolioEsgChart(portfolioEsgData); 72 | 73 | } 74 | }); 75 | 76 | } 77 | }); 78 | 79 | } 80 | 81 | function portfolioEsgChart(portfolioEsgData) { 82 | 83 | var data = portfolioEsgData; 84 | 85 | d3.select("#portfolio-esg-chart").remove(); 86 | $('.esg-chart').html(function() { 87 | return ''; 88 | }); 89 | 90 | //set the dimensions and margins of the graph 91 | var margin = { top: 20, right: 20, bottom: 60, left: 85 }; 92 | var width = 300 - (margin.left + margin.right); 93 | var height = 220 - (margin.top + margin.bottom); 94 | 95 | 96 | //set the ranges 97 | var y = d3.scaleBand() 98 | .range([height, 0]) 99 | .padding(0.1); 100 | 101 | var x = d3.scaleLinear() 102 | .range([0, width]); 103 | 104 | //append the svg object to the body of the page 105 | //append a 'group' element to 'svg' 106 | //moves the 'group' element to the top left margin 107 | var svg = d3.select("#portfolio-esg-chart") 108 | .attr("width", width + margin.left + margin.right) 109 | .attr("height", height + margin.top + margin.bottom) 110 | .append("g") 111 | .attr("transform", 112 | "translate(" + margin.left + "," + margin.top + ")"); 113 | 114 | //format the data 115 | data.forEach(function(d) { 116 | d.value = +d.value; 117 | }); 118 | 119 | //scale the range of the data in the domains 120 | x.domain([0, d3.max(data, function(d){ return d.value; })]) 121 | y.domain(data.map(function(d) { return d.esg; })); 122 | 123 | //append the rectangles for the bar chart 124 | svg.selectAll(".bar") 125 | .data(data) 126 | .enter().append("rect") 127 | .attr("class", "bar") 128 | .attr("y", function(d) { return y(d.esg); }) 129 | .attr("height", y.bandwidth()) 130 | .transition() 131 | .duration(500) 132 | .attr("width", function(d) {return x(d.value); } ) 133 | .attr('fill', '#93C4FB') 134 | 135 | //add the x Axis 136 | svg.append("g") 137 | .attr("transform", "translate(0," + height + ")") 138 | .call(d3.axisBottom(x).ticks(4)); 139 | 140 | //add the y Axis 141 | svg.append("g") 142 | .call(d3.axisLeft(y)); 143 | 144 | svg.exit().remove() 145 | } 146 | -------------------------------------------------------------------------------- /static/js/benchmarks.js: -------------------------------------------------------------------------------- 1 | function benchmarksChartData() { 2 | 3 | var chartData = []; 4 | var esgControversyScores = []; 5 | var esgEnvironmentalScores = []; 6 | var esgGovernanceScores = []; 7 | var esgSocialScores = []; 8 | var esgSustainabilityScores = []; 9 | var keys = []; 10 | 11 | for (var key in esgData) { 12 | 13 | var benchmarksObj = esgData[key]; 14 | var scoresList = []; 15 | 16 | var esgControversyScore = benchmarksObj["esg_Controversy"] / 10; 17 | esgControversyScores.push(esgControversyScore); 18 | 19 | var esgEnvironmentalScore = benchmarksObj["esg_Environmental"] / 10; 20 | esgEnvironmentalScores.push(esgEnvironmentalScore); 21 | 22 | var esgGovernanceScore = benchmarksObj["esg_Governance"] / 10; 23 | esgGovernanceScores.push(esgGovernanceScore); 24 | 25 | var esgSocialScore = benchmarksObj["esg_Social"] / 10; 26 | esgSocialScores.push(esgSocialScore); 27 | 28 | var esgSustainabilityScore = benchmarksObj["esg_Sustainability"] / 10; 29 | esgSustainabilityScores.push(esgSustainabilityScore); 30 | 31 | keys.push(key); 32 | 33 | } 34 | 35 | var esgControversyEntry = [esgControversyScores, "Controversy"]; 36 | var esgEnvironmentalEntry = [esgEnvironmentalScores, "Environmental"]; 37 | var esgGovernanceEntry = [esgGovernanceScores, "Governance"]; 38 | var esgSocialEntry = [esgSocialScores, "Social"]; 39 | var esgSustainabilityEntry = [esgSustainabilityScores, "Sustainability"]; 40 | chartData.push(esgSustainabilityEntry, esgControversyEntry, esgEnvironmentalEntry, esgSocialEntry, esgGovernanceEntry); 41 | 42 | return [chartData, keys]; 43 | 44 | } 45 | 46 | 47 | function benchmarksChart() { 48 | 49 | var benchmarksData = benchmarksChartData(); 50 | var data = benchmarksData[0]; 51 | var keys = benchmarksData[1]; 52 | 53 | //benchmarks bar chart 54 | //source bar graph: http://www.carbondesignsystem.com/data-vis/bar-graph/code 55 | var formatPercent = d3.format(".0%"); 56 | var colors = d3.scaleOrdinal(). 57 | range(['#00A78F', '#3b1a40', '#473793', '#3c6df0', '#56D2BB']); 58 | var keyColors = ['#00A78F', '#3b1a40', '#473793', '#3c6df0', '#56D2BB']; 59 | 60 | var margin = { 61 | top: 30, 62 | right: 20, 63 | bottom: 60, 64 | left: 65 65 | }; 66 | var width = 800 - (margin.left + margin.right); 67 | var height = 300 - (margin.top + margin.bottom); 68 | var labelOffset = 50; 69 | var axisOffset = 16; 70 | 71 | 72 | //group Scale 73 | var x0 = d3.scaleBand(). 74 | rangeRound([0, width]). 75 | domain(data.map(function(d) { 76 | return d[1]; 77 | })). 78 | paddingInner(0.3); 79 | 80 | var x1 = d3.scaleBand(). 81 | rangeRound([0, x0.bandwidth()]). 82 | domain(d3.range(data[0][0].length)). 83 | padding(0.05); 84 | 85 | var y = d3.scaleLinear(). 86 | range([height, 0]). 87 | domain([0, d3.max(data, function(d) { 88 | return d3.max(d[0], function(i) { 89 | return i; 90 | }); 91 | })]); 92 | 93 | //set the axes 94 | var xAxis = d3.axisBottom(). 95 | scale(x0). 96 | tickSize(0); 97 | 98 | var yAxis = d3.axisLeft(). 99 | ticks(5). 100 | tickSize(-width). 101 | scale(y.nice()). 102 | tickFormat(formatPercent); 103 | 104 | //set up SVG with initial transform to avoid repeat positioning 105 | var svg = d3.select('#benchmarks-chart'). 106 | attr('class', 'graph-groupedbar'). 107 | attr('width', width + (margin.left + margin.right)). 108 | attr('height', height + (margin.top + margin.bottom)). 109 | append('g'). 110 | attr('class', 'group-container-groupedbar'). 111 | attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'). 112 | attr('font-family', 'ibm-plex-sans'); 113 | 114 | //add Y axis 115 | svg.append('g'). 116 | attr('class', 'axis-groupedbar y-groupedbar'). 117 | attr('stroke-dasharray', '4'). 118 | call(yAxis). 119 | selectAll('text'). 120 | attr("x", -axisOffset). 121 | attr('font-family', 'ibm-plex-sans'); 122 | 123 | //add X axis 124 | svg.append('g'). 125 | attr('class', 'axis-groupedbar x-groupedbar'). 126 | attr('transform', 'translate(0, ' + height + ')'). 127 | call(xAxis). 128 | selectAll('text'). 129 | attr("y", axisOffset). 130 | attr('font-family', 'ibm-plex-sans'); 131 | 132 | var count = 0; 133 | svg.append('g'). 134 | selectAll('g'). 135 | data(data). 136 | enter().append('g'). 137 | attr('transform', function(d) { 138 | return 'translate(' + x0(d[1]) + ', 0)'; 139 | }). 140 | selectAll('rect'). 141 | data(function(d) { 142 | count++; 143 | return d[0].map(function(key, index) { 144 | return { 145 | key: key, 146 | index: index, 147 | series: count 148 | }; 149 | 150 | }); 151 | }). 152 | enter().append('rect'). 153 | attr('class', 'bar'). 154 | attr('x', function(d) { 155 | return x1(d.index); 156 | }). 157 | attr('width', x1.bandwidth()). 158 | attr('fill', function(d) { 159 | return colors(d.index); 160 | }). 161 | attr('y', function(d) { 162 | return y(d.key); 163 | }). 164 | attr('height', function(d) { 165 | return height - y(d.key); 166 | }); 167 | 168 | 169 | //add key 170 | $('.benchmarks-key').html(function() { 171 | var str = '' 172 | for (var i = 0; i < keys.length; i++) { 173 | str = str + '

' + keys[i] + '

'; 174 | } 175 | return str; 176 | }); 177 | 178 | } 179 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | line-height: 1.5rem; 4 | } 5 | 6 | .container { 7 | padding-top: 48px; 8 | } 9 | 10 | .header-bar { 11 | height: 48px; 12 | width: 100vw; 13 | box-sizing: border-box; 14 | background-color: #444444; 15 | color: white; 16 | display: -ms-flexbox; 17 | display: flex; 18 | -ms-flex-direction: row; 19 | flex-direction: row; 20 | padding: 0 calc((100vw - 900px) / 2); 21 | position: fixed; 22 | top: 0px; 23 | left: 0px; 24 | z-index: 1000; 25 | } 26 | 27 | .header-bar__home { 28 | background-color: #444444; 29 | width: 100%; 30 | padding-right: 264px; 31 | } 32 | 33 | .header-bar__home__button { 34 | width: 100%; 35 | height: 48px; 36 | line-height: 48px; 37 | background-color: #444444; 38 | color: white; 39 | font-weight: 300; 40 | font-size: 14px; 41 | letter-spacing: 0.5px; 42 | padding: 0px 0px 0px 0px; 43 | } 44 | 45 | .header-bar__new { 46 | width: 216px; 47 | float: right; 48 | text-align: right; 49 | position: absolute; 50 | right: 0px; 51 | top: 0px; 52 | height: 48px; 53 | line-height: 48px; 54 | margin-right: calc((100vw - 900px) / 2); 55 | } 56 | 57 | .new-analysis { 58 | position: absolute; 59 | top: 0px; 60 | right: 0px; 61 | height: 32px; 62 | margin: 8px 0; 63 | line-height: 32px; 64 | box-sizing: border-box; 65 | padding: 0px 16px; 66 | border: 1px solid white; 67 | color: white; 68 | -webkit-user-select: none; 69 | -moz-user-select: none; 70 | -ms-user-select: none; 71 | user-select: none; 72 | background-color: #444444; 73 | } 74 | 75 | h2 { 76 | font-size: 2rem; 77 | } 78 | 79 | .analysis-input { 80 | display: block; 81 | background-color: black; 82 | color: white; 83 | padding: 0 calc((100vw - 900px) / 2); 84 | position: relative; 85 | padding-bottom: 24px; 86 | } 87 | 88 | .heading-input { 89 | font-size: 28px; 90 | padding: 24px 0; 91 | } 92 | 93 | .enter-portfolio select { 94 | color: white; 95 | box-shadow: inset 0 -1px 0 0 white; 96 | -webkit-appearance: none; 97 | border-radius: 0; 98 | max-width: 28.25rem; 99 | height: 2rem; 100 | padding: .25rem 0; 101 | border: none; 102 | background-color: transparent; 103 | font: inherit; 104 | width: 70%; 105 | } 106 | 107 | .get-started { 108 | justify-content: center; 109 | flex-basis: 216px; 110 | flex-grow: 0; 111 | text-align: left; 112 | padding-top: 48px; 113 | padding-bottom: 24px; 114 | display: flex; 115 | } 116 | 117 | #run-button { 118 | height: 40px; 119 | width: auto; 120 | box-sizing: border-box; 121 | font-size: 16px; 122 | border: 2px solid #464646; 123 | color: white; 124 | user-select: none; 125 | cursor: pointer; 126 | font-weight: 600; 127 | padding: 0 1rem; 128 | font: inherit; 129 | background-color: black; 130 | } 131 | 132 | .loader { 133 | padding: 32px; 134 | display: none; 135 | width: 100%; 136 | height: auto; 137 | top: 0px; 138 | left: 0px; 139 | justify-content: center; 140 | align-items: center; 141 | margin-top: 0px; 142 | z-index: 100; 143 | } 144 | 145 | .loader img { 146 | width: 44px; 147 | display: block; 148 | } 149 | 150 | #filmstrip { 151 | width: 100%; 152 | position: fixed; 153 | bottom: 0; 154 | } 155 | 156 | .tab-content { 157 | display: none; 158 | } 159 | 160 | .content { 161 | height: 350px; 162 | } 163 | 164 | #composition { 165 | display: block; 166 | text-align: center; 167 | } 168 | 169 | #esg-categories { 170 | display: none; 171 | text-align: center; 172 | } 173 | 174 | #search { 175 | display: none; 176 | text-align: center; 177 | background: white; 178 | margin: 10px; 179 | height: 70vh; 180 | } 181 | 182 | #benchmarks { 183 | display: none; 184 | text-align: center; 185 | } 186 | 187 | #non-esg { 188 | display: none; 189 | text-align: center; 190 | } 191 | 192 | #improve { 193 | display: none; 194 | } 195 | 196 | #by-asset { 197 | display: block; 198 | } 199 | 200 | #by-industry { 201 | display: none; 202 | } 203 | 204 | #by-geography { 205 | display: none; 206 | } 207 | 208 | #portfolio-table { 209 | display: none; 210 | margin-bottom: 5rem; 211 | } 212 | 213 | .pointer { 214 | cursor: pointer; 215 | } 216 | 217 | .list-view-icon { 218 | float: right; 219 | padding-right: 15px; 220 | cursor: pointer; 221 | position: initial; 222 | } 223 | 224 | .chart-view-icon { 225 | float: right; 226 | padding-right: 15px; 227 | } 228 | 229 | .composition-table { 230 | overflow: scroll; 231 | height: 300px; 232 | } 233 | 234 | th svg { 235 | opacity: 0; 236 | } 237 | 238 | th[data-active] svg { 239 | opacity: 1; 240 | } 241 | 242 | .text-percent-color { 243 | display: inline; 244 | font-size: 1.4rem; 245 | color: #473793; 246 | } 247 | 248 | .improve-text { 249 | text-align: left; 250 | margin-top: 30px; 251 | color: white; 252 | background: #051b75; 253 | padding-left: 60px; 254 | padding-bottom: 20px; 255 | padding-top: 10px; 256 | } 257 | 258 | .make-a-change { 259 | justify-content: center; 260 | flex-basis: 216px; 261 | flex-grow: 0; 262 | text-align: left; 263 | padding-top: 48px; 264 | padding-bottom: 24px; 265 | display: flex; 266 | } 267 | 268 | .change-button { 269 | height: 40px; 270 | width: auto; 271 | box-sizing: border-box; 272 | font-size: 16px; 273 | border: 2px solid #051b75; 274 | color: #051b75; 275 | user-select: none; 276 | cursor: pointer; 277 | font-weight: 600; 278 | padding: 0 1rem; 279 | padding-top: 5px; 280 | text-decoration: none; 281 | background-color: white; 282 | } 283 | -------------------------------------------------------------------------------- /investmentportfolio.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import argparse 4 | from dotenv import load_dotenv 5 | import os 6 | import datetime 7 | 8 | #Initalize Investment Portfolio Service credentials to find on Bluemix otherwise from .env file 9 | if 'VCAP_SERVICES' in os.environ: 10 | vcap_servicesData = json.loads(os.environ['VCAP_SERVICES']) 11 | # Log the fact that we successfully found some service information. 12 | print("Got vcap_servicesData\n") 13 | #print(vcap_servicesData) 14 | # Look for the IP service instance. 15 | IP_W_username=vcap_servicesData['fss-portfolio-service'][0]['credentials']['writer']['userid'] 16 | IP_W_password=vcap_servicesData['fss-portfolio-service'][0]['credentials']['writer']['password'] 17 | IP_R_username=vcap_servicesData['fss-portfolio-service'][0]['credentials']['reader']['userid'] 18 | IP_R_password=vcap_servicesData['fss-portfolio-service'][0]['credentials']['reader']['password'] 19 | # Log the fact that we successfully found credentials 20 | print("Got IP credentials\n") 21 | else: 22 | load_dotenv(os.path.join(os.path.dirname(__file__), ".env")) 23 | IP_W_username=os.environ.get("CRED_PORTFOLIO_USERID_W") 24 | IP_W_password=os.environ.get("CRED_PORTFOLIO_PWD_W") 25 | IP_R_username=os.environ.get("CRED_PORTFOLIO_USERID_R") 26 | IP_R_password=os.environ.get("CRED_PORTFOLIO_PWD_R") 27 | 28 | def Get_Portfolios(name=""): 29 | """ 30 | Retreives portfolio data by calling the Investment Portfolio service 31 | """ 32 | print ("Get Portfolios") 33 | #call the url 34 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + name 35 | headers = { 36 | 'accept': "application/json", 37 | 'content-type': "application/json" 38 | } 39 | get_data = requests.get(BASEURL, auth=(IP_R_username, IP_R_password), headers=headers) 40 | print("Investment Portfolio status: " + str(get_data.status_code)) 41 | # return json data 42 | data = get_data.json() 43 | return data 44 | 45 | def Get_Portfolio_Holdings(Portfolio,latest=True): 46 | """ 47 | Retreives holdinga data from the Investment Portfolio service for the Portfolio 48 | """ 49 | print ("Get Portfolio Holdings for " + Portfolio) 50 | #construct the url 51 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + Portfolio + "/holdings" 52 | if latest: 53 | BASEURL += "?latest=true" 54 | #call the url 55 | headers = { 56 | 'accept': "application/json", 57 | 'content-type': "application/json" 58 | } 59 | get_data = requests.get(BASEURL, auth=(IP_R_username, IP_R_password), headers=headers) 60 | print("Investment Portfolio - Get Portfolio Holdings status: " + str(get_data.status_code)) 61 | #return json data 62 | data = get_data.json() 63 | return data 64 | 65 | def Get_Portfolios_by_Selector(selector,value): 66 | """ 67 | Retreives portfolio data by calling the Investment Portfolio service 68 | """ 69 | print ("Get Portfolios by Selector") 70 | #call the url 71 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/_find" 72 | headers = { 73 | 'accept': "application/json", 74 | 'content-type': "application/json" 75 | } 76 | s = { 77 | 'dataSelector':{ 78 | selector:value 79 | } 80 | } 81 | 82 | get_data = requests.post(BASEURL, auth=(IP_R_username, IP_R_password), headers=headers, data=json.dumps(s)) 83 | print("Investment Portfolio status: " + str(get_data.status_code)) 84 | # return json data 85 | data = get_data.json() 86 | return data 87 | 88 | def Get_Holdings_by_Selector(portfolio,selector,value): 89 | """ 90 | Retreives portfolio holdings data by calling the Investment Portfolio service 91 | """ 92 | print ("Get Portfolios by Selector") 93 | #call the url 94 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + portfolio + "/holdings/_find" 95 | headers = { 96 | 'accept': "application/json", 97 | 'content-type': "application/json" 98 | } 99 | s = { 100 | 'dataSelector':{ 101 | str(selector):str(value) 102 | } 103 | } 104 | get_data = requests.post(BASEURL, auth=(IP_R_username, IP_R_password), headers=headers, data=json.dumps(s)) 105 | print("Investment Portfolio status: " + str(get_data.status_code)) 106 | # return json data 107 | data = get_data.json() 108 | return data 109 | 110 | def Create_Portfolio(Portfolio): 111 | """ 112 | Creates portfolio in the database. 113 | """ 114 | print('Create_Portfolio') 115 | #construct the url 116 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios" 117 | headers = { 118 | 'Content-Type': "application/json", 119 | 'Accept': "application/json" 120 | } 121 | get_data = requests.post(BASEURL, auth=(IP_W_username, IP_W_password), headers=headers, data=json.dumps(Portfolio)) 122 | 123 | #print the status and returned json 124 | status = get_data.status_code 125 | print("Investment Portfolio status: " + str(status)) 126 | 127 | if status != 200: 128 | return get_data 129 | else: 130 | data = get_data.json() 131 | return json.dumps(data, indent=4, sort_keys=True) 132 | 133 | def Create_Portfolio_Holdings(portfolio_name,holdings): 134 | """ 135 | Creates portfolio holdings. 136 | """ 137 | print('Create_Portfolio holdings') 138 | timestamp = '{:%Y-%m-%dT%H:%M:%S.%fZ}'.format(datetime.datetime.now()) 139 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + portfolio_name + "/holdings" 140 | headers = { 141 | 'Content-Type': "application/json", 142 | 'Accept': "application/json" 143 | } 144 | data = { 145 | 'timestamp': timestamp, 146 | 'holdings': holdings 147 | } 148 | get_data = requests.post(BASEURL, auth=(IP_W_username, IP_W_password), headers=headers, data=json.dumps(data)) 149 | 150 | #print the status and returned json 151 | status = get_data.status_code 152 | print("Investment Portfolio Holding status: " + str(status)) 153 | 154 | if status != 200: 155 | return get_data.json() 156 | else: 157 | data = get_data.json() 158 | return json.dumps(data, indent=4, sort_keys=True) 159 | 160 | def Delete_Portfolio(portfolio_name,timestamp,rev): 161 | """ 162 | Deletes a portfolio. 163 | """ 164 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + str(portfolio_name) + "/" + str(timestamp) + "?rev=" + str(rev) 165 | headers = { 166 | 'Content-Type': "application/json", 167 | 'Accept': "application/json", 168 | 'Authorization':"Basic aGV5cmVsc2VuZG9udHJhdGlyc2VudWVuOjM4NDUzNTZjNzY2NTY4NTA0YjkyYzM3ZDJiOGVkZTkzZWYzMTg0NTA=" 169 | } 170 | res = requests.delete(BASEURL, auth=(IP_W_username, IP_W_password), headers=headers) 171 | 172 | #print the status and returned json 173 | status = res.status_code 174 | print("Investment Portfolio delete status: " + str(status)) 175 | 176 | if status != 200: 177 | return res 178 | else: 179 | return "Portfolio " + portfolio_name + " deleted successfully." 180 | 181 | def Delete_Portfolio_Holdings(portfolio_name,timestamp,rev): 182 | """ 183 | Deletes portfolio holdings. 184 | """ 185 | BASEURL = "https://investment-portfolio.mybluemix.net/api/v1/portfolios/" + str(portfolio_name) + "/holdings/" + str(timestamp) + "?rev=" + str(rev) 186 | print(BASEURL) 187 | headers = { 188 | 'Content-Type': "application/x-www-form-urlencoded", 189 | 'Accept': "application/json", 190 | 'authorization':'Basic REPLACE_BASIC_AUTH' 191 | } 192 | res = requests.delete(BASEURL, auth=(IP_W_username, IP_W_password), headers=headers) 193 | 194 | #print the status and returned json 195 | status = res.status_code 196 | print("Investment Portfolio holdings delete status: " + str(status)) 197 | 198 | if status != 200: 199 | return res 200 | else: 201 | return "Portfolio " + portfolio_name + " deleted successfully." 202 | -------------------------------------------------------------------------------- /static/js/lib/jquery.autocomplete.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ajax Autocomplete for jQuery, version 1.2.7 3 | * (c) 2013 Tomas Kirda 4 | * 5 | * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 6 | * For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/ 7 | * 8 | */ 9 | (function(e){"function"===typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function g(a,b){var c=function(){},c={autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:g.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:c,onSearchComplete:c,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",lookupFilter:function(a,b,c){return-1!== 10 | a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(a){return"string"===typeof a?e.parseJSON(a):a}};this.element=a;this.el=e(a);this.suggestions=[];this.badQueries=[];this.selectedIndex=-1;this.currentValue=this.element.value;this.intervalId=0;this.cachedResponse=[];this.onChange=this.onChangeInterval=null;this.isLocal=this.ignoreValueChange=!1;this.suggestionsContainer=null;this.options=e.extend({},c,b);this.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"}; 11 | this.initialize();this.setOptions(b)}var h={extend:function(a,b){return e.extend(a,b)},createNode:function(a){var b=document.createElement("div");b.innerHTML=a;return b.firstChild}};g.utils=h;e.Autocomplete=g;g.formatResult=function(a,b){var c="("+b.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\)","g"),"\\$1")+")";return a.value.replace(RegExp(c,"gi"),"$1")};g.prototype={killerFn:null,initialize:function(){var a=this,b="."+a.classes.suggestion,c=a.classes.selected, 12 | d=a.options,f;a.element.setAttribute("autocomplete","off");a.killerFn=function(b){0===e(b.target).closest("."+a.options.containerClass).length&&(a.killSuggestions(),a.disableKillerFn())};if(!d.width||"auto"===d.width)d.width=a.el.outerWidth();a.suggestionsContainer=g.utils.createNode('');f=e(a.suggestionsContainer);f.appendTo(d.appendTo).width(d.width);f.on("mouseover.autocomplete",b,function(){a.activate(e(this).data("index"))}); 13 | f.on("mouseout.autocomplete",function(){a.selectedIndex=-1;f.children("."+c).removeClass(c)});f.on("click.autocomplete",b,function(){a.select(e(this).data("index"),!1)});a.fixPosition();if(window.opera)a.el.on("keypress.autocomplete",function(b){a.onKeyPress(b)});else a.el.on("keydown.autocomplete",function(b){a.onKeyPress(b)});a.el.on("keyup.autocomplete",function(b){a.onKeyUp(b)});a.el.on("blur.autocomplete",function(){a.onBlur()});a.el.on("focus.autocomplete",function(){a.fixPosition()})},onBlur:function(){this.enableKillerFn()}, 14 | setOptions:function(a){var b=this.options;h.extend(b,a);if(this.isLocal=e.isArray(b.lookup))b.lookup=this.verifySuggestionsFormat(b.lookup);e(this.suggestionsContainer).css({"max-height":b.maxHeight+"px",width:b.width+"px","z-index":b.zIndex})},clearCache:function(){this.cachedResponse=[];this.badQueries=[]},clear:function(){this.clearCache();this.currentValue=null;this.suggestions=[]},disable:function(){this.disabled=!0},enable:function(){this.disabled=!1},fixPosition:function(){var a;"body"===this.options.appendTo&& 15 | (a=this.el.offset(),e(this.suggestionsContainer).css({top:a.top+this.el.outerHeight()+"px",left:a.left+"px"}))},enableKillerFn:function(){e(document).on("click.autocomplete",this.killerFn)},disableKillerFn:function(){e(document).off("click.autocomplete",this.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions();a.intervalId=window.setInterval(function(){a.hide();a.stopKillSuggestions()},300)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},onKeyPress:function(a){if(!this.disabled&& 16 | !this.visible&&40===a.keyCode&&this.currentValue)this.suggest();else if(!this.disabled&&this.visible){switch(a.keyCode){case 27:this.el.val(this.currentValue);this.hide();break;case 9:case 13:if(-1===this.selectedIndex){this.hide();return}this.select(this.selectedIndex,13===a.keyCode);if(9===a.keyCode&&!1===this.options.tabDisabled)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}a.stopImmediatePropagation();a.preventDefault()}},onKeyUp:function(a){var b=this; 17 | if(!b.disabled){switch(a.keyCode){case 38:case 40:return}clearInterval(b.onChangeInterval);if(b.currentValue!==b.el.val())if(0'+a(e,b)+""});f.html(g).show();this.visible=!0;this.options.autoSelectFirst&&(this.selectedIndex=0,f.children().first().addClass(d))}},verifySuggestionsFormat:function(a){return a.length&&"string"=== 21 | typeof a[0]?e.map(a,function(a){return{value:a,data:null}}):a},processResponse:function(a,b){var c=this.options,d=c.transformResult(a,b);d.suggestions=this.verifySuggestionsFormat(d.suggestions);c.noCache||(this.cachedResponse[d[c.paramName]]=d,0===d.suggestions.length&&this.badQueries.push(d[c.paramName]));b===this.getQuery(this.currentValue)&&(this.suggestions=d.suggestions,this.suggest())},activate:function(a){var b=this.classes.selected,c=e(this.suggestionsContainer),d=c.children();c.children("."+ 22 | b).removeClass(b);this.selectedIndex=a;return-1!==this.selectedIndex&&d.length>this.selectedIndex?(a=d.get(this.selectedIndex),e(a).addClass(b),a):null},select:function(a,b){var c=this.suggestions[a];c&&(this.el.val(c),this.ignoreValueChange=b,this.hide(),this.onSelect(a))},moveUp:function(){-1!==this.selectedIndex&&(0===this.selectedIndex?(e(this.suggestionsContainer).children().first().removeClass(this.classes.selected),this.selectedIndex=-1,this.el.val(this.currentValue)):this.adjustScroll(this.selectedIndex- 23 | 1))},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(a){var b=this.activate(a),c,d;b&&(b=b.offsetTop,c=e(this.suggestionsContainer).scrollTop(),d=c+this.options.maxHeight-25,bd&&e(this.suggestionsContainer).scrollTop(b-this.options.maxHeight+25),this.el.val(this.getValue(this.suggestions[a].value)))},onSelect:function(a){var b=this.options.onSelect;a=this.suggestions[a]; 24 | this.el.val(this.getValue(a.value));e.isFunction(b)&&b.call(this.element,a)},getValue:function(a){var b=this.options.delimiter,c;if(!b)return a;c=this.currentValue;b=c.split(b);return 1===b.length?a:c.substr(0,c.length-b[b.length-1].length)+a},dispose:function(){this.el.off(".autocomplete").removeData("autocomplete");this.disableKillerFn();e(this.suggestionsContainer).remove()}};e.fn.autocomplete=function(a,b){return 0===arguments.length?this.first().data("autocomplete"):this.each(function(){var c= 25 | e(this),d=c.data("autocomplete");if("string"===typeof a){if(d&&"function"===typeof d[a])d[a](b)}else d&&d.dispose&&d.dispose(),d=new g(this,a),c.data("autocomplete",d)})}}); -------------------------------------------------------------------------------- /static/js/display.js: -------------------------------------------------------------------------------- 1 | $(".dropdown") 2 | .change(function() { 3 | var str = ""; 4 | var selected = $('.bx--graph-header select').find(":selected").text(); 5 | 6 | if (selected == "Asset Allocation") { 7 | document.getElementById('by-asset').style.display = "block"; 8 | document.getElementById('by-industry').style.display = "none"; 9 | document.getElementById('by-geography').style.display = "none"; 10 | 11 | } else if (selected == "Industry") { 12 | document.getElementById('by-asset').style.display = "none"; 13 | document.getElementById('by-industry').style.display = "block"; 14 | document.getElementById('by-geography').style.display = "none"; 15 | industryChart(); 16 | 17 | } else if (selected == "Geography") { 18 | document.getElementById('by-asset').style.display = "none"; 19 | document.getElementById('by-industry').style.display = "none"; 20 | document.getElementById('by-geography').style.display = "block"; 21 | geographyChart(); 22 | } 23 | 24 | }) 25 | .change(); 26 | 27 | function displayCompositionTable() { 28 | document.getElementById('portfolio-table').style.display = "block"; 29 | document.getElementById('composition-chart').style.display = "none"; 30 | 31 | var PortfolioTableIcon = document.getElementById('Portfolio-Table-1'); 32 | var PortfolioCompositionIcon = document.getElementById('Portfolio-Composition-1'); 33 | PortfolioTableIcon.style.fill = '#0F6FFF'; 34 | PortfolioCompositionIcon.style.fill = '#767676'; 35 | 36 | } 37 | 38 | function displayCompositionChart() { 39 | document.getElementById('portfolio-table').style.display = "none"; 40 | document.getElementById('composition-chart').style.display = "block"; 41 | 42 | var PortfolioTableIcon = document.getElementById('Portfolio-Table-1'); 43 | var PortfolioCompositionIcon = document.getElementById('Portfolio-Composition-1'); 44 | PortfolioTableIcon.style.fill = '#767676'; 45 | PortfolioCompositionIcon.style.fill = '#0F6FFF'; 46 | } 47 | 48 | 49 | function displayComposition() { 50 | //show the selected layout 51 | document.getElementById('composition').style.display = "block"; 52 | document.getElementById('esg-categories').style.display = "none"; 53 | document.getElementById('search').style.display = "none"; 54 | document.getElementById('benchmarks').style.display = "none"; 55 | document.getElementById('non-esg').style.display = "none"; 56 | document.getElementById('improve').style.display = "none"; 57 | 58 | //highlight on filmstrip 59 | document.getElementById('Tile-1').style.stroke = "#5596e6"; 60 | document.getElementById('Tile-2').style.stroke = "#DCDCDC"; 61 | document.getElementById('Tile-3').style.stroke = "#DCDCDC"; 62 | document.getElementById('Tile-4').style.stroke = "#DCDCDC"; 63 | document.getElementById('Tile-5').style.stroke = "#DCDCDC"; 64 | document.getElementById('Tile-6').style.stroke = "#DCDCDC"; 65 | 66 | document.getElementById('Tile-1').style.strokeWidth = "3"; 67 | document.getElementById('Tile-2').style.strokeWidth = "1"; 68 | document.getElementById('Tile-3').style.strokeWidth = "1"; 69 | document.getElementById('Tile-4').style.strokeWidth = "1"; 70 | document.getElementById('Tile-5').style.strokeWidth = "1"; 71 | document.getElementById('Tile-6').style.strokeWidth = "1"; 72 | } 73 | 74 | 75 | 76 | function displayEsgCategories() { 77 | //show the selected layout 78 | document.getElementById('composition').style.display = "none"; 79 | document.getElementById('esg-categories').style.display = "block"; 80 | document.getElementById('search').style.display = "none"; 81 | document.getElementById('benchmarks').style.display = "none"; 82 | document.getElementById('non-esg').style.display = "none"; 83 | document.getElementById('improve').style.display = "none"; 84 | 85 | //update chart 86 | esgGaugeCharts(); 87 | 88 | //highlight on filmstrip 89 | document.getElementById('Tile-1').style.stroke = "#DCDCDC"; 90 | document.getElementById('Tile-2').style.stroke = "#5596e6"; 91 | document.getElementById('Tile-3').style.stroke = "#DCDCDC"; 92 | document.getElementById('Tile-4').style.stroke = "#DCDCDC"; 93 | document.getElementById('Tile-5').style.stroke = "#DCDCDC"; 94 | document.getElementById('Tile-6').style.stroke = "#DCDCDC"; 95 | 96 | document.getElementById('Tile-1').style.strokeWidth = "1"; 97 | document.getElementById('Tile-2').style.strokeWidth = "3"; 98 | document.getElementById('Tile-3').style.strokeWidth = "1"; 99 | document.getElementById('Tile-4').style.strokeWidth = "1"; 100 | document.getElementById('Tile-5').style.strokeWidth = "1"; 101 | document.getElementById('Tile-6').style.strokeWidth = "1"; 102 | } 103 | 104 | function displaySearch() { 105 | //show the selected layout 106 | document.getElementById('composition').style.display = "none"; 107 | document.getElementById('esg-categories').style.display = "none"; 108 | document.getElementById('search').style.display = "block"; 109 | document.getElementById('benchmarks').style.display = "none"; 110 | document.getElementById('non-esg').style.display = "none"; 111 | document.getElementById('improve').style.display = "none"; 112 | 113 | clearSearch(); 114 | searchField(); 115 | 116 | //highlight on filmstrip 117 | document.getElementById('Tile-1').style.stroke = "#DCDCDC"; 118 | document.getElementById('Tile-2').style.stroke = "#DCDCDC"; 119 | document.getElementById('Tile-3').style.stroke = "#5596e6"; 120 | document.getElementById('Tile-4').style.stroke = "#DCDCDC"; 121 | document.getElementById('Tile-5').style.stroke = "#DCDCDC"; 122 | document.getElementById('Tile-6').style.stroke = "#DCDCDC"; 123 | 124 | document.getElementById('Tile-1').style.strokeWidth = "1"; 125 | document.getElementById('Tile-2').style.strokeWidth = "1"; 126 | document.getElementById('Tile-3').style.strokeWidth = "3"; 127 | document.getElementById('Tile-4').style.strokeWidth = "1"; 128 | document.getElementById('Tile-5').style.strokeWidth = "1"; 129 | document.getElementById('Tile-6').style.strokeWidth = "1"; 130 | } 131 | 132 | function displayBenchmarks() { 133 | //show the selected layout 134 | document.getElementById('composition').style.display = "none"; 135 | document.getElementById('esg-categories').style.display = "none"; 136 | document.getElementById('search').style.display = "none"; 137 | document.getElementById('benchmarks').style.display = "block"; 138 | document.getElementById('non-esg').style.display = "none"; 139 | document.getElementById('improve').style.display = "none"; 140 | 141 | benchmarksChart(); 142 | 143 | //highlight on filmstrip 144 | document.getElementById('Tile-1').style.stroke = "#DCDCDC"; 145 | document.getElementById('Tile-2').style.stroke = "#DCDCDC"; 146 | document.getElementById('Tile-3').style.stroke = "#DCDCDC"; 147 | document.getElementById('Tile-4').style.stroke = "#5596e6"; 148 | document.getElementById('Tile-5').style.stroke = "#DCDCDC"; 149 | document.getElementById('Tile-6').style.stroke = "#DCDCDC"; 150 | 151 | document.getElementById('Tile-1').style.strokeWidth = "1"; 152 | document.getElementById('Tile-2').style.strokeWidth = "1"; 153 | document.getElementById('Tile-3').style.strokeWidth = "1"; 154 | document.getElementById('Tile-4').style.strokeWidth = "3"; 155 | document.getElementById('Tile-5').style.strokeWidth = "1"; 156 | document.getElementById('Tile-6').style.strokeWidth = "1"; 157 | } 158 | 159 | function displayNonEsg() { 160 | //show the selected layout 161 | document.getElementById('composition').style.display = "none"; 162 | document.getElementById('esg-categories').style.display = "none"; 163 | document.getElementById('search').style.display = "none"; 164 | document.getElementById('benchmarks').style.display = "none"; 165 | document.getElementById('non-esg').style.display = "block"; 166 | document.getElementById('improve').style.display = "none"; 167 | 168 | //display charts 169 | sinCharts(); 170 | 171 | //highlight on filmstrip 172 | document.getElementById('Tile-1').style.stroke = "#DCDCDC"; 173 | document.getElementById('Tile-2').style.stroke = "#DCDCDC"; 174 | document.getElementById('Tile-3').style.stroke = "#DCDCDC"; 175 | document.getElementById('Tile-4').style.stroke = "#DCDCDC"; 176 | document.getElementById('Tile-5').style.stroke = "#5596e6"; 177 | document.getElementById('Tile-6').style.stroke = "#DCDCDC"; 178 | 179 | document.getElementById('Tile-1').style.strokeWidth = "1"; 180 | document.getElementById('Tile-2').style.strokeWidth = "1"; 181 | document.getElementById('Tile-3').style.strokeWidth = "1"; 182 | document.getElementById('Tile-4').style.strokeWidth = "1"; 183 | document.getElementById('Tile-5').style.strokeWidth = "3"; 184 | document.getElementById('Tile-6').style.strokeWidth = "1"; 185 | 186 | } 187 | 188 | function displayImprove() { 189 | //show the selected layout 190 | document.getElementById('composition').style.display = "none"; 191 | document.getElementById('esg-categories').style.display = "none"; 192 | document.getElementById('search').style.display = "none"; 193 | document.getElementById('benchmarks').style.display = "none"; 194 | document.getElementById('non-esg').style.display = "none"; 195 | document.getElementById('improve').style.display = "block"; 196 | 197 | //highlight on filmstrip 198 | document.getElementById('Tile-1').style.stroke = "#DCDCDC"; 199 | document.getElementById('Tile-2').style.stroke = "#DCDCDC"; 200 | document.getElementById('Tile-3').style.stroke = "#DCDCDC"; 201 | document.getElementById('Tile-4').style.stroke = "#DCDCDC"; 202 | document.getElementById('Tile-5').style.stroke = "#DCDCDC"; 203 | document.getElementById('Tile-6').style.stroke = "#5596e6"; 204 | 205 | document.getElementById('Tile-1').style.strokeWidth = "1"; 206 | document.getElementById('Tile-2').style.strokeWidth = "1"; 207 | document.getElementById('Tile-3').style.strokeWidth = "1"; 208 | document.getElementById('Tile-4').style.strokeWidth = "1"; 209 | document.getElementById('Tile-5').style.strokeWidth = "1"; 210 | document.getElementById('Tile-6').style.strokeWidth = "3"; 211 | 212 | } 213 | -------------------------------------------------------------------------------- /static/js/geography/d3-tip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * d3.tip 3 | * Copyright (c) 2013 Justin Palmer 4 | * 5 | * Tooltips for d3.js SVG visualizations 6 | */ 7 | // eslint-disable-next-line no-extra-semi 8 | ;(function(root, factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD. Register as an anonymous module with d3 as a dependency. 11 | define([ 12 | 'd3-collection', 13 | 'd3-selection' 14 | ], factory) 15 | } else if (typeof module === 'object' && module.exports) { 16 | /* eslint-disable global-require */ 17 | // CommonJS 18 | var d3Collection = require('d3-collection'), 19 | d3Selection = require('d3-selection') 20 | module.exports = factory(d3Collection, d3Selection) 21 | /* eslint-enable global-require */ 22 | } else { 23 | // Browser global. 24 | var d3 = root.d3 25 | // eslint-disable-next-line no-param-reassign 26 | root.d3.tip = factory(d3, d3) 27 | } 28 | }(this, function(d3Collection, d3Selection) { 29 | // Public - contructs a new tooltip 30 | // 31 | // Returns a tip 32 | return function() { 33 | var direction = d3TipDirection, 34 | offset = d3TipOffset, 35 | html = d3TipHTML, 36 | node = initNode(), 37 | svg = null, 38 | point = null, 39 | target = null 40 | 41 | function tip(vis) { 42 | svg = getSVGNode(vis) 43 | if (!svg) return 44 | point = svg.createSVGPoint() 45 | document.body.appendChild(node) 46 | } 47 | 48 | // Public - show the tooltip on the screen 49 | // 50 | // Returns a tip 51 | tip.show = function() { 52 | var args = Array.prototype.slice.call(arguments) 53 | if (args[args.length - 1] instanceof SVGElement) target = args.pop() 54 | 55 | var content = html.apply(this, args), 56 | poffset = offset.apply(this, args), 57 | dir = direction.apply(this, args), 58 | nodel = getNodeEl(), 59 | i = directions.length, 60 | coords, 61 | scrollTop = document.documentElement.scrollTop || 62 | document.body.scrollTop, 63 | scrollLeft = document.documentElement.scrollLeft || 64 | document.body.scrollLeft 65 | 66 | nodel.html(content) 67 | .style('opacity', 1).style('pointer-events', 'all') 68 | 69 | while (i--) nodel.classed(directions[i], false) 70 | coords = directionCallbacks.get(dir).apply(this) 71 | nodel.classed(dir, true) 72 | .style('top', (coords.top + poffset[0]) + scrollTop + 'px') 73 | .style('left', (coords.left + poffset[1]) + scrollLeft + 'px') 74 | 75 | return tip 76 | } 77 | 78 | // Public - hide the tooltip 79 | // 80 | // Returns a tip 81 | tip.hide = function() { 82 | var nodel = getNodeEl() 83 | nodel.style('opacity', 0).style('pointer-events', 'none') 84 | return tip 85 | } 86 | 87 | // Public: Proxy attr calls to the d3 tip container. 88 | // Sets or gets attribute value. 89 | // 90 | // n - name of the attribute 91 | // v - value of the attribute 92 | // 93 | // Returns tip or attribute value 94 | // eslint-disable-next-line no-unused-vars 95 | tip.attr = function(n, v) { 96 | if (arguments.length < 2 && typeof n === 'string') { 97 | return getNodeEl().attr(n) 98 | } 99 | 100 | var args = Array.prototype.slice.call(arguments) 101 | d3Selection.selection.prototype.attr.apply(getNodeEl(), args) 102 | return tip 103 | } 104 | 105 | // Public: Proxy style calls to the d3 tip container. 106 | // Sets or gets a style value. 107 | // 108 | // n - name of the property 109 | // v - value of the property 110 | // 111 | // Returns tip or style property value 112 | // eslint-disable-next-line no-unused-vars 113 | tip.style = function(n, v) { 114 | if (arguments.length < 2 && typeof n === 'string') { 115 | return getNodeEl().style(n) 116 | } 117 | 118 | var args = Array.prototype.slice.call(arguments) 119 | d3Selection.selection.prototype.style.apply(getNodeEl(), args) 120 | return tip 121 | } 122 | 123 | // Public: Set or get the direction of the tooltip 124 | // 125 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 126 | // sw(southwest), ne(northeast) or se(southeast) 127 | // 128 | // Returns tip or direction 129 | tip.direction = function(v) { 130 | if (!arguments.length) return direction 131 | direction = v == null ? v : functor(v) 132 | 133 | return tip 134 | } 135 | 136 | // Public: Sets or gets the offset of the tip 137 | // 138 | // v - Array of [x, y] offset 139 | // 140 | // Returns offset or 141 | tip.offset = function(v) { 142 | if (!arguments.length) return offset 143 | offset = v == null ? v : functor(v) 144 | 145 | return tip 146 | } 147 | 148 | // Public: sets or gets the html value of the tooltip 149 | // 150 | // v - String value of the tip 151 | // 152 | // Returns html value or tip 153 | tip.html = function(v) { 154 | if (!arguments.length) return html 155 | html = v == null ? v : functor(v) 156 | 157 | return tip 158 | } 159 | 160 | // Public: destroys the tooltip and removes it from the DOM 161 | // 162 | // Returns a tip 163 | tip.destroy = function() { 164 | if (node) { 165 | getNodeEl().remove() 166 | node = null 167 | } 168 | return tip 169 | } 170 | 171 | function d3TipDirection() { return 'n' } 172 | function d3TipOffset() { return [0, 0] } 173 | function d3TipHTML() { return ' ' } 174 | 175 | var directionCallbacks = d3Collection.map({ 176 | n: directionNorth, 177 | s: directionSouth, 178 | e: directionEast, 179 | w: directionWest, 180 | nw: directionNorthWest, 181 | ne: directionNorthEast, 182 | sw: directionSouthWest, 183 | se: directionSouthEast 184 | }), 185 | directions = directionCallbacks.keys() 186 | 187 | function directionNorth() { 188 | var bbox = getScreenBBox() 189 | return { 190 | top: bbox.n.y - node.offsetHeight, 191 | left: bbox.n.x - node.offsetWidth / 2 192 | } 193 | } 194 | 195 | function directionSouth() { 196 | var bbox = getScreenBBox() 197 | return { 198 | top: bbox.s.y, 199 | left: bbox.s.x - node.offsetWidth / 2 200 | } 201 | } 202 | 203 | function directionEast() { 204 | var bbox = getScreenBBox() 205 | return { 206 | top: bbox.e.y - node.offsetHeight / 2, 207 | left: bbox.e.x 208 | } 209 | } 210 | 211 | function directionWest() { 212 | var bbox = getScreenBBox() 213 | return { 214 | top: bbox.w.y - node.offsetHeight / 2, 215 | left: bbox.w.x - node.offsetWidth 216 | } 217 | } 218 | 219 | function directionNorthWest() { 220 | var bbox = getScreenBBox() 221 | return { 222 | top: bbox.nw.y - node.offsetHeight, 223 | left: bbox.nw.x - node.offsetWidth 224 | } 225 | } 226 | 227 | function directionNorthEast() { 228 | var bbox = getScreenBBox() 229 | return { 230 | top: bbox.ne.y - node.offsetHeight, 231 | left: bbox.ne.x 232 | } 233 | } 234 | 235 | function directionSouthWest() { 236 | var bbox = getScreenBBox() 237 | return { 238 | top: bbox.sw.y, 239 | left: bbox.sw.x - node.offsetWidth 240 | } 241 | } 242 | 243 | function directionSouthEast() { 244 | var bbox = getScreenBBox() 245 | return { 246 | top: bbox.se.y, 247 | left: bbox.se.x 248 | } 249 | } 250 | 251 | function initNode() { 252 | var div = d3Selection.select(document.createElement('div')) 253 | div 254 | .style('position', 'absolute') 255 | .style('top', 0) 256 | .style('opacity', 0) 257 | .style('pointer-events', 'none') 258 | .style('box-sizing', 'border-box') 259 | 260 | return div.node() 261 | } 262 | 263 | function getSVGNode(element) { 264 | var svgNode = element.node() 265 | if (!svgNode) return null 266 | if (svgNode.tagName.toLowerCase() === 'svg') return svgNode 267 | return svgNode.ownerSVGElement 268 | } 269 | 270 | function getNodeEl() { 271 | if (node == null) { 272 | node = initNode() 273 | // re-add node to DOM 274 | document.body.appendChild(node) 275 | } 276 | return d3Selection.select(node) 277 | } 278 | 279 | // Private - gets the screen coordinates of a shape 280 | // 281 | // Given a shape on the screen, will return an SVGPoint for the directions 282 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), 283 | // nw(northwest), sw(southwest). 284 | // 285 | // +-+-+ 286 | // | | 287 | // + + 288 | // | | 289 | // +-+-+ 290 | // 291 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 292 | function getScreenBBox() { 293 | var targetel = target || d3Selection.event.target 294 | 295 | while (targetel.getScreenCTM == null && targetel.parentNode == null) { 296 | targetel = targetel.parentNode 297 | } 298 | 299 | var bbox = {}, 300 | matrix = targetel.getScreenCTM(), 301 | tbbox = targetel.getBBox(), 302 | width = tbbox.width, 303 | height = tbbox.height, 304 | x = tbbox.x, 305 | y = tbbox.y 306 | 307 | point.x = x 308 | point.y = y 309 | bbox.nw = point.matrixTransform(matrix) 310 | point.x += width 311 | bbox.ne = point.matrixTransform(matrix) 312 | point.y += height 313 | bbox.se = point.matrixTransform(matrix) 314 | point.x -= width 315 | bbox.sw = point.matrixTransform(matrix) 316 | point.y -= height / 2 317 | bbox.w = point.matrixTransform(matrix) 318 | point.x += width 319 | bbox.e = point.matrixTransform(matrix) 320 | point.x -= width / 2 321 | point.y -= height / 2 322 | bbox.n = point.matrixTransform(matrix) 323 | point.y += height 324 | bbox.s = point.matrixTransform(matrix) 325 | 326 | return bbox 327 | } 328 | 329 | // Private - replace D3JS 3.X d3.functor() function 330 | function functor(v) { 331 | return typeof v === 'function' ? v : function() { 332 | return v 333 | } 334 | } 335 | 336 | return tip 337 | } 338 | // eslint-disable-next-line semi 339 | })); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This repository is no longer maintained :warning: 2 | 3 | > This repository will not be updated. The repository will be kept available in read-only mode. 4 | 5 | [![Build Status](https://travis-ci.org/IBM/Analyze-Investment-Portfolio.svg?branch=master)](https://travis-ci.org/IBM/Analyze-Investment-Portfolio) 6 | 7 | 8 | # Portfolio Analyzer 9 | 10 | ## Understanding true exposures and properties by looking through funds 11 | 12 | - The **Investment Portfolio service** is used to manage and store portfolios and financial security information such as the set of eligible investments, benchmarks, and user portfolios. 13 | 14 | This code pattern is designed for developers with interest in creating financial applications pertaining to investment portfolios and understanding the true underlying exposures that are often obscured when investing in composite investment securities such as ETFs and Mutual Funds When the reader has completed this journey, they will understand how to: 15 | 16 | * Load and retrieve data from the Investment Portfolio service 17 | * Computes all of the exposures associated with a portfolio that may have composite investments present 18 | * Construct a user interface with D3 graphics to display analysis 19 | 20 | ![](doc-images/arch.png) 21 | 22 | # Flow 23 | 24 | 1. User uploads a portfolio for analysis. 25 | 2. User selects a portfolio to analyze. 26 | 3. The code pattern queries the user's portfolio, any look-through information on funds held in the user portfolio, and calculates all of the analytics necessary to populate the GUI. 27 | 28 | ## Included Components 29 | + [Investment Portfolio](https://cloud.ibm.com/catalog/services/investment-portfolio) The Investment Portfolio service lets you store, update, and query your investment portfolios and associated holdings using flexible object definitions 30 | > The Investment Portfolio service is available for free on [IBM Cloud](https://cloud.ibm.com) 31 | 32 | ## Featured Technology 33 | 34 | * [Python](https://www.python.org/downloads/): Python is a programming language that lets you work more quickly and integrate your systems more effectively. 35 | * [JQuery](https://jquery.com): jQuery is a cross-platform JavaScript library designed to simplify the client-side 36 | scripting of HTML. 37 | * [D3.js](https://d3js.org/): D3 is a JavaScript library for visualizing data with HTML, SVG, and CSS. 38 | * [Carbon Design System](https://www.carbondesignsystem.com/): Carbon is the design system for IBM Cloud products with a series of individual styles, components, and guidelines used for creating unified UI. 39 | 40 | 41 | ## Prerequisite 42 | 43 | - [IBM Cloud account](https://cloud.ibm.com/registration/?target=%2Fdashboard%2Fapps) 44 | 45 | 46 | # Steps 47 | 48 | Use the ``Deploy to IBM Cloud`` button **OR** create the services and run ``Run Locally``. 49 | 50 | # Deploy to IBM Cloud 51 | 52 | Create an [IBM Cloud account](https://cloud.ibm.com/registration/?target=%2Fdashboard%2Fapps) and directly deploy the application using the button below. 53 | 54 | [![Deploy to IBM Cloud](https://cloud.ibm.com/devops/setup/deploy/button.png)](https://cloud.ibm.com/devops/setup/deploy?repository=https://github.com/IBM/Analyze-Investment-Portfolio) 55 | 56 | 57 | # Running the Application Locally 58 | Follow these steps to setup and run this developer journey. The steps are described in detail below. 59 | 60 | ## Prerequisite 61 | - [Python](https://www.python.org/downloads/) 62 | 63 | ## Steps to run locally 64 | 1. [Clone the repo](#1-clone-the-repo) 65 | 2. [Create Investment Portfolio service](#2-create-investment-portfolio-service) 66 | 3. [Configure .env file](#4-configure-env-file) 67 | 4. [Run Application](#5-run-application) 68 | 5. [Upload Holdings](#6-uploading-holdings) 69 | 6. [Analyze Portfolio](#7-analyze-portfolio) 70 | 7. [Deploy to IBM Cloud](#7-deploy-to-ibm-cloud) 71 | 72 | ## 1. Clone the repo 73 | 74 | Clone the `Analyze Investment Portfolio code` locally. In a terminal, run: 75 | 76 | `$ git clone https://github.com/IBM/Analyze-Investment-Portfolio` 77 | 78 | ## 2. Create Investment Portfolio service 79 | 80 | Create the following services in IBM Cloud. This services is part of `Free` plan. 81 | 82 | * [**Investment Portfolio**](https://cloud.ibm.com/catalog/services/investment-portfolio) 83 | 84 | 85 | ## 3. Configure .env file 86 | 87 | Create a `.env` file in the root directory of your clone of the project repository by copying the sample `.env.example` file using the following command in terminal: 88 | 89 | ```none 90 | cp .env.example .env 91 | ``` 92 | 93 | > Most files systems regard files with a "." at the front as hidden files. If you are on a Windows system, you should be able to use either [GitBash](https://gitforwindows.org/) or [Xcopy](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/xcopy) 94 | 95 | You will need to update the credentials with the IBM Cloud credentials for the services you created in [Step 2](#2-create-investment-portfolio-service). 96 | 97 | The `.env` file will look something like the following: 98 | 99 | ```none 100 | # Investment Portfolio 101 | CRED_PORTFOLIO_USERID_W= 102 | CRED_PORTFOLIO_PWD_W= 103 | CRED_PORTFOLIO_USERID_R= 104 | CRED_PORTFOLIO_PWD_R= 105 | 106 | ``` 107 | 108 | ## 4. Run Application 109 | 110 | In your terminal, cd into this project's root directory 111 | + Run `pip install -r requirements.txt` to install the app's dependencies 112 | + Run `python run.py` 113 | + Access the running app in a browser at 114 | 115 | ![](doc-images/home.png) 116 | 117 | ## 5. Upload Holdings 118 | 119 | Once the application is running, the first step is to upload a file that will be used to create a portfolio or a series of portfolios in the Investment Portfolio service. We use the file format of the Algorithmics Risk Service (ARS) import file as many production clients are already used to that format. 120 | 121 | You can use the `SamplePortfolio.csv` file in this repository. 122 | 123 | - The column labeled "UNIQUE ID" must refer to the unique identifier of the asset in our system. 124 | - The "NAME" column will hold the display name of the asset. 125 | - "POSITION UNITS" column holds the quantity. 126 | - "PORTFOLIO" indicates which portfolio the asset belongs to. 127 | 128 | The code will create a portfolio for each unique element found in the "PORTFOLIO" column. Future releases of this code will take into account a portfolio hierarchy, but currently each portfolio is entirely independent of each other. 129 | 130 | Some notes: 131 | - The portfolio will be loaded as 100-asset chunks as there are currently limitations on POST request sizing. 132 | - The portfolio will be tagged as type = 'look through portfolio' to distinguish between any other portfolios that may exist in the system. 133 | 134 | *Note: You can navigate to /api/look_through_delete to delete all portfolios that have been loaded into the service using this application. Specifically, it looks for portfolios with "type = look through portfolio". You can always start over this way, but be careful to not access this if you've done considerable work.* 135 | 136 | ## 6. Analyze Portfolio 137 | 138 | The next step is to use the application to call the **/api/portfolio-analyze** endpoint. This will perform the following: 139 | - Read the portfolio that the user selected and determine if any composite securities are included as part of that portfolio. 140 | - Retrieve any additional portfolios associated with composite investments in the user's portfolio. E.g. If a user has invested in the S&P 500 ETF (IVV), 141 | we'll want to retrieve all of the holdings and data associated with IVV. 142 | - Computes the equivalent dollar weight of each security touched by the user's portfolio, and calculate all true exposures looking through to the funds. 143 | - Return a payload of the results 144 | - Pipe results into several visualization and front-end components for the user to observe. 145 | 146 | Some notes: 147 | - The/ portfolio-analyze endpoint returns data formatted for the GUI packaged with this code pattern. Additional endpoints are included to make smaller, more custom request types depending on your use case. 148 | - Instruments not found will be ignored. 149 | 150 | #### Visualizations 151 | 152 | * View portfolio composition by asset allocation 153 | 154 | ![](doc-images/assets.png) 155 | 156 | * View portfolio composition by industry 157 | 158 | ![](doc-images/industry.png) 159 | 160 | * View portfolio composition by geography 161 | 162 | ![](doc-images/geography.png) 163 | 164 | * View portfolio composition table 165 | 166 | ![](doc-images/table.png) 167 | 168 | * View portfolio composition by ESG Category 169 | 170 | ![](doc-images/esg.png) 171 | 172 | * Search for a holding in your portfolio and get key information pertaining to it 173 | 174 | ![](doc-images/search.png) 175 | 176 | * Compare your ESG scores with couple benchmarks 177 | 178 | ![](doc-images/benchmarks.png) 179 | 180 | * View "sin" investments in your portfolio 181 | 182 | ![](doc-images/sin.png) 183 | 184 | 185 | ## 7. Deploy to IBM Cloud 186 | 187 | Edit the `manifest.yml` file in the folder that contains your code and replace with a unique name for your application. The name that you specify determines the application's URL, such as `your-application-name.mybluemix.net`. Additionally - update the service names so they match what you have in the IBM Cloud. The relevant portion of the `manifest.yml` file looks like the following: 188 | 189 | ```none 190 | declared-services: 191 | Investment-Portfolio: 192 | label: fss-portfolio-service 193 | plan: fss-portfolio-service-free-plan 194 | applications: 195 | - path: . 196 | memory: 128M 197 | instances: 1 198 | name: Portfolio-Analyze 199 | disk_quota: 1024M 200 | domain: mybluemix.net 201 | services: 202 | - Investment-Portfolio 203 | buildpack: python_buildpack 204 | ``` 205 | 206 | You can push the app to IBM Cloud using [IBM Cloud CLI](https://cloud.ibm.com/docs/cli). This will use the services and application name in the `manifest.yml` file. From your root directory login into IBM Cloud using CLI: 207 | ``` 208 | bx login 209 | ``` 210 | And push the app to IBM Cloud: 211 | ``` 212 | bx push 213 | ``` 214 | 215 | # Troubleshooting 216 | 217 | * To troubleshoot your IBM Cloud application, use the logs. To see the logs, run: 218 | 219 | ```bash 220 | bx logs --recent 221 | ``` 222 | 223 | * If you are running locally - inspect your environment variables closely to confirm they match. Try running each service as standalone: 224 | 225 | ```bash 226 | python InvestmentPortfolio.py 227 | ``` 228 | 229 | # Learn more 230 | 234 | 235 | # License 236 | 237 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](https://www.apache.org/licenses/LICENSE-2.0.txt). 238 | 239 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 240 | -------------------------------------------------------------------------------- /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 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /static/js/geography/world_investment.json: -------------------------------------------------------------------------------- 1 | [{"id": "CHN", "investments": "0", "name": "China"}, {"id": "IND", "investments": "0", "name": "India"}, {"id": "USA", "investments": 618579.1905671995, "name": "United States"}, {"id": "IDN", "investments": "0", "name": "Indonesia"}, {"id": "BRA", "investments": "0", "name": "Brazil"}, {"id": "PAK", "investments": "0", "name": "Pakistan"}, {"id": "BGD", "investments": "0", "name": "Bangladesh"}, {"id": "NGA", "investments": "0", "name": "Nigeria"}, {"id": "RUS", "investments": "0", "name": "Russia"}, {"id": "JPN", "investments": "0", "name": "Japan"}, {"id": "MEX", "investments": "0", "name": "Mexico"}, {"id": "PHL", "investments": "0", "name": "Philippines"}, {"id": "VNM", "investments": "0", "name": "Vietnam"}, {"id": "ETH", "investments": "0", "name": "Ethiopia"}, {"id": "DEU", "investments": "0", "name": "Germany"}, {"id": "EGY", "investments": "0", "name": "Egypt"}, {"id": "TUR", "investments": "0", "name": "Turkey"}, {"id": "COD", "investments": "0", "name": "Congo, Democratic Republic of the"}, {"id": "IRN", "investments": "0", "name": "Iran"}, {"id": "THA", "investments": "0", "name": "Thailand"}, {"id": "FRA", "investments": "0", "name": "France"}, {"id": "GBR", "investments": "0", "name": "United Kingdom"}, {"id": "ITA", "investments": "0", "name": "Italy"}, {"id": "MMR", "investments": "0", "name": "Burma"}, {"id": "ZAF", "investments": "0", "name": "South Africa"}, {"id": "KOR", "investments": "0", "name": "Korea, South"}, {"id": "UKR", "investments": "0", "name": "Ukraine"}, {"id": "COL", "investments": "0", "name": "Colombia"}, {"id": "SDN", "investments": "0", "name": "Sudan"}, {"id": "TZA", "investments": "0", "name": "Tanzania"}, {"id": "ARG", "investments": "0", "name": "Argentina"}, {"id": "ESP", "investments": "0", "name": "Spain"}, {"id": "KEN", "investments": "0", "name": "Kenya"}, {"id": "POL", "investments": "0", "name": "Poland"}, {"id": "DZA", "investments": "0", "name": "Algeria"}, {"id": "CAN", "investments": 4400.0, "name": "Canada"}, {"id": "UGA", "investments": "0", "name": "Uganda"}, {"id": "MAR", "investments": "0", "name": "Morocco"}, {"id": "PER", "investments": "0", "name": "Peru"}, {"id": "IRQ", "investments": "0", "name": "Iraq"}, {"id": "SAU", "investments": "0", "name": "Saudi Arabia"}, {"id": "AFG", "investments": "0", "name": "Afghanistan"}, {"id": "NPL", "investments": "0", "name": "Nepal"}, {"id": "UZB", "investments": "0", "name": "Uzbekistan"}, {"id": "VEN", "investments": "0", "name": "Venezuela"}, {"id": "MYS", "investments": "0", "name": "Malaysia"}, {"id": "GHA", "investments": "0", "name": "Ghana"}, {"id": "YEM", "investments": "0", "name": "Yemen"}, {"id": "TWN", "investments": "0", "name": "Taiwan"}, {"id": "PRK", "investments": "0", "name": "Korea, North"}, {"id": "SYR", "investments": "0", "name": "Syria"}, {"id": "ROU", "investments": "0", "name": "Romania"}, {"id": "MOZ", "investments": "0", "name": "Mozambique"}, {"id": "AUS", "investments": "0", "name": "Australia"}, {"id": "LKA", "investments": "0", "name": "Sri Lanka"}, {"id": "MDG", "investments": "0", "name": "Madagascar"}, {"id": "CIV", "investments": "0", "name": "Cote d'Ivoire"}, {"id": "CMR", "investments": "0", "name": "Cameroon"}, {"id": "NLD", "investments": 361.84958656000003, "name": "Netherlands"}, {"id": "CHL", "investments": "0", "name": "Chile"}, {"id": "BFA", "investments": "0", "name": "Burkina Faso"}, {"id": "NER", "investments": "0", "name": "Niger"}, {"id": "KAZ", "investments": "0", "name": "Kazakhstan"}, {"id": "MWI", "investments": "0", "name": "Malawi"}, {"id": "ECU", "investments": "0", "name": "Ecuador"}, {"id": "KHM", "investments": "0", "name": "Cambodia"}, {"id": "SEN", "investments": "0", "name": "Senegal"}, {"id": "MLI", "investments": "0", "name": "Mali"}, {"id": "GTM", "investments": "0", "name": "Guatemala"}, {"id": "AGO", "investments": "0", "name": "Angola"}, {"id": "ZMB", "investments": "0", "name": "Zambia"}, {"id": "ZWE", "investments": "0", "name": "Zimbabwe"}, {"id": "CUB", "investments": "0", "name": "Cuba"}, {"id": "RWA", "investments": "0", "name": "Rwanda"}, {"id": "GRC", "investments": "0", "name": "Greece"}, {"id": "PRT", "investments": "0", "name": "Portugal"}, {"id": "TUN", "investments": "0", "name": "Tunisia"}, {"id": "TCD", "investments": "0", "name": "Chad"}, {"id": "BEL", "investments": "0", "name": "Belgium"}, {"id": "GIN", "investments": "0", "name": "Guinea"}, {"id": "CZE", "investments": "0", "name": "Czech Republic"}, {"id": "SOM", "investments": "0", "name": "Somalia"}, {"id": "BOL", "investments": "0", "name": "Bolivia"}, {"id": "HUN", "investments": "0", "name": "Hungary"}, {"id": "BDI", "investments": "0", "name": "Burundi"}, {"id": "DOM", "investments": "0", "name": "Dominican Republic"}, {"id": "BLR", "investments": "0", "name": "Belarus"}, {"id": "HTI", "investments": "0", "name": "Haiti"}, {"id": "SWE", "investments": "0", "name": "Sweden"}, {"id": "BEN", "investments": "0", "name": "Benin"}, {"id": "AZE", "investments": "0", "name": "Azerbaijan"}, {"id": "AUT", "investments": "0", "name": "Austria"}, {"id": "HND", "investments": "0", "name": "Honduras"}, {"id": "CHE", "investments": "0", "name": "Switzerland"}, {"id": "TJK", "investments": "0", "name": "Tajikistan"}, {"id": "ISR", "investments": "0", "name": "Israel"}, {"id": "SRB", "investments": "0", "name": "Serbia"}, {"id": "BGR", "investments": "0", "name": "Bulgaria"}, {"id": "HKG", "investments": "0", "name": "Hong Kong"}, {"id": "LAO", "investments": "0", "name": "Laos"}, {"id": "LBY", "investments": "0", "name": "Libya"}, {"id": "JOR", "investments": "0", "name": "Jordan"}, {"id": "PRY", "investments": "0", "name": "Paraguay"}, {"id": "TGO", "investments": "0", "name": "Togo"}, {"id": "PNG", "investments": "0", "name": "Papua New Guinea"}, {"id": "SLV", "investments": "0", "name": "El Salvador"}, {"id": "NIC", "investments": "0", "name": "Nicaragua"}, {"id": "ERI", "investments": "0", "name": "Eritrea"}, {"id": "DNK", "investments": "0", "name": "Denmark"}, {"id": "KGZ", "investments": "0", "name": "Kyrgyzstan"}, {"id": "SVK", "investments": "0", "name": "Slovakia"}, {"id": "FIN", "investments": "0", "name": "Finland"}, {"id": "SLE", "investments": "0", "name": "Sierra Leone"}, {"id": "ARE", "investments": "0", "name": "United Arab Emirates"}, {"id": "TKM", "investments": "0", "name": "Turkmenistan"}, {"id": "CAF", "investments": "0", "name": "Central African Republic"}, {"id": "SGP", "investments": "0", "name": "Singapore"}, {"id": "NOR", "investments": "0", "name": "Norway"}, {"id": "BIH", "investments": "0", "name": "Bosnia and Herzegovina"}, {"id": "GEO", "investments": "0", "name": "Georgia"}, {"id": "CRI", "investments": "0", "name": "Costa Rica"}, {"id": "HRV", "investments": "0", "name": "Croatia"}, {"id": "MDA", "investments": "0", "name": "Moldova"}, {"id": "NZL", "investments": "0", "name": "New Zealand"}, {"id": "IRL", "investments": "0", "name": "Ireland"}, {"id": "COG", "investments": "0", "name": "Congo, Republic of the"}, {"id": "LBN", "investments": "0", "name": "Lebanon"}, {"id": "PRI", "investments": "0", "name": "Puerto Rico"}, {"id": "LBR", "investments": "0", "name": "Liberia"}, {"id": "ALB", "investments": "0", "name": "Albania"}, {"id": "LTU", "investments": "0", "name": "Lithuania"}, {"id": "URY", "investments": "0", "name": "Uruguay"}, {"id": "PAN", "investments": "0", "name": "Panama"}, {"id": "MRT", "investments": "0", "name": "Mauritania"}, {"id": "MNG", "investments": "0", "name": "Mongolia"}, {"id": "OMN", "investments": "0", "name": "Oman"}, {"id": "ARM", "investments": "0", "name": "Armenia"}, {"id": "JAM", "investments": "0", "name": "Jamaica"}, {"id": "KWT", "investments": "0", "name": "Kuwait"}, {"id": "PSE", "investments": "0", "name": "West Bank"}, {"id": "LVA", "investments": "0", "name": "Latvia"}, {"id": "NAM", "investments": "0", "name": "Namibia"}, {"id": "MKD", "investments": "0", "name": "Macedonia"}, {"id": "BWA", "investments": "0", "name": "Botswana"}, {"id": "SVN", "investments": "0", "name": "Slovenia"}, {"id": "LSO", "investments": "0", "name": "Lesotho"}, {"id": "GMB", "investments": "0", "name": "Gambia, The"}, {"id": "KWT", "investments": "0", "name": "Kosovo"}, {"id": "149", "investments": "0", "name": "Gaza Strip"}, {"id": "GNB", "investments": "0", "name": "Guinea-Bissau"}, {"id": "GAB", "investments": "0", "name": "Gabon"}, {"id": "SWZ", "investments": "0", "name": "Swaziland"}, {"id": "153", "investments": "0", "name": "Mauritius"}, {"id": "EST", "investments": "0", "name": "Estonia"}, {"id": "TTO", "investments": "0", "name": "Trinidad and Tobago"}, {"id": "TLS", "investments": "0", "name": "Timor-Leste"}, {"id": "CYP", "investments": "0", "name": "Cyprus"}, {"id": "FJI", "investments": "0", "name": "Fiji"}, {"id": "QAT", "investments": "0", "name": "Qatar"}, {"id": "160", "investments": "0", "name": "Comoros"}, {"id": "GUY", "investments": "0", "name": "Guyana"}, {"id": "DJI", "investments": "0", "name": "Djibouti"}, {"id": "163", "investments": "0", "name": "Bahrain"}, {"id": "BTN", "investments": "0", "name": "Bhutan"}, {"id": "MNE", "investments": "0", "name": "Montenegro"}, {"id": "GNQ", "investments": "0", "name": "Equatorial Guinea"}, {"id": "SLB", "investments": "0", "name": "Solomon Islands"}, {"id": "168", "investments": "0", "name": "Macau"}, {"id": "169", "investments": "0", "name": "Cape Verde"}, {"id": "LUX", "investments": "0", "name": "Luxembourg"}, {"id": "ESH", "investments": "0", "name": "Western Sahara"}, {"id": "SUR", "investments": "0", "name": "Suriname"}, {"id": "173", "investments": "0", "name": "Malta"}, {"id": "174", "investments": "0", "name": "Maldives"}, {"id": "BRN", "investments": "0", "name": "Brunei"}, {"id": "BLZ", "investments": "0", "name": "Belize"}, {"id": "BHS", "investments": "0", "name": "Bahamas, The"}, {"id": "ISL", "investments": "0", "name": "Iceland"}, {"id": "179", "investments": "0", "name": "French Polynesia"}, {"id": "180", "investments": "0", "name": "Barbados"}, {"id": "181", "investments": "0", "name": "Mayotte"}, {"id": "NCL", "investments": "0", "name": "New Caledonia"}, {"id": "183", "investments": "0", "name": "Netherlands Antilles"}, {"id": "VUT", "investments": "0", "name": "Vanuatu"}, {"id": "185", "investments": "0", "name": "Samoa"}, {"id": "186", "investments": "0", "name": "Sao Tome and Principe"}, {"id": "187", "investments": "0", "name": "Saint Lucia"}, {"id": "188", "investments": "0", "name": "Tonga"}, {"id": "189", "investments": "0", "name": "Virgin Islands"}, {"id": "190", "investments": "0", "name": "Grenada"}, {"id": "191", "investments": "0", "name": "Micronesia, Federated States of"}, {"id": "192", "investments": "0", "name": "Aruba"}, {"id": "193", "investments": "0", "name": "Saint Vincent and the Grenadines"}, {"id": "194", "investments": "0", "name": "Kiribati"}, {"id": "195", "investments": "0", "name": "Jersey"}, {"id": "196", "investments": "0", "name": "Seychelles"}, {"id": "197", "investments": "0", "name": "Antigua and Barbuda"}, {"id": "198", "investments": "0", "name": "Andorra"}, {"id": "199", "investments": "0", "name": "Isle of Man"}, {"id": "DOM", "investments": "0", "name": "Dominica"}, {"id": "201", "investments": "0", "name": "Bermuda"}, {"id": "202", "investments": "0", "name": "American Samoa"}, {"id": "203", "investments": "0", "name": "Marshall Islands"}, {"id": "204", "investments": "0", "name": "Guernsey"}, {"id": "GRL", "investments": "0", "name": "Greenland"}, {"id": "206", "investments": "0", "name": "Cayman Islands"}, {"id": "207", "investments": "0", "name": "Saint Kitts and Nevis"}, {"id": "208", "investments": "0", "name": "Faroe Islands"}, {"id": "209", "investments": "0", "name": "Northern Mariana Islands"}, {"id": "210", "investments": "0", "name": "Liechtenstein"}, {"id": "211", "investments": "0", "name": "San Marino"}, {"id": "212", "investments": "0", "name": "Monaco"}, {"id": "213", "investments": "0", "name": "Saint Martin"}, {"id": "214", "investments": "0", "name": "Gibraltar"}, {"id": "215", "investments": "0", "name": "British Virgin Islands"}, {"id": "216", "investments": "0", "name": "Turks and Caicos Islands"}, {"id": "217", "investments": "0", "name": "Palau"}, {"id": "218", "investments": "0", "name": "Akrotiri"}, {"id": "219", "investments": "0", "name": "Dhekelia"}, {"id": "220", "investments": "0", "name": "Wallis and Futuna"}, {"id": "221", "investments": "0", "name": "Anguilla"}, {"id": "222", "investments": "0", "name": "Nauru"}, {"id": "223", "investments": "0", "name": "Cook Islands"}, {"id": "224", "investments": "0", "name": "Tuvalu"}, {"id": "225", "investments": "0", "name": "Saint Helena, Ascension, and Tristan da Cunha"}, {"id": "226", "investments": "0", "name": "Saint Barthelemy"}, {"id": "227", "investments": "0", "name": "Saint Pierre and Miquelon"}, {"id": "228", "investments": "0", "name": "Montserrat"}, {"id": "FLK", "investments": "0", "name": "Falkland Islands (Islas Malvinas)"}, {"id": "230", "investments": "0", "name": "Norfolk Island"}, {"id": "231", "investments": "0", "name": "Svalbard"}, {"id": "232", "investments": "0", "name": "Christmas Island"}, {"id": "233", "investments": "0", "name": "Tokelau"}, {"id": "234", "investments": "0", "name": "Niue"}, {"id": "235", "investments": "829", "name": "Holy See (Vatican City)"}, {"id": "236", "investments": "596", "name": "Cocos (Keeling) Islands"}, {"id": "237", "investments": "48", "name": "Pitcairn Islands"}, {"id": "ATA", "investments": "0", "name": "Antarctica"}, {"id": "ATF", "investments": "0", "name": "French Southern and Antarctic Lands"}, {"id": "SDS", "investments": "0", "name": "South Sudan"}, {"id": "ABV", "investments": "0", "name": "Somaliland"}, {"id": "OSA", "investments": "0", "name": "Kosovo"}] -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, jsonify, json, url_for, request, redirect, Response, flash, abort, make_response, send_file 2 | import requests 3 | import io 4 | import os 5 | import csv 6 | import datetime 7 | import investmentportfolio 8 | 9 | print ('Running Portfolio Analyze') 10 | app = Flask(__name__) 11 | 12 | # On IBM Cloud, get the port number from the environment variable VCAP_APP_PORT 13 | # When running this app on the local machine, default the port to 8080 14 | port = int(os.getenv('VCAP_APP_PORT', 8080)) 15 | host='0.0.0.0' 16 | 17 | #======================================MAIN PAGES====================================== 18 | @app.route('/') 19 | def run(): 20 | """ 21 | Load the site page 22 | """ 23 | return render_template('index.html') 24 | 25 | #======================================DATABASE MANAGEMENT============================== 26 | @app.route('/api/upload', methods=['POST']) 27 | def portfolio_from_csv(): 28 | """ 29 | Loads a portfolio in Algo Risk Service (ARS) format into the Investment Portfolio service. 30 | """ 31 | holdings = { 32 | 'timestamp':'{:%Y-%m-%dT%H:%M:%S.%fZ}'.format(datetime.datetime.now()), 33 | 'holdings':[] 34 | } 35 | data = json.loads(request.data) 36 | data = [row.split(',') for row in data] 37 | headers = data[0] 38 | #Loop through and segregate each portfolio by its identifier (there may be multiple in the file) 39 | #Column 1 (not 0) is the ID column. Column 5 is the PORTFOLIO column... 40 | portfolios = {} 41 | navs = {} 42 | unique_id_col = headers.index("UNIQUE ID") 43 | id_type_col = headers.index("ID TYPE") 44 | name_col = headers.index("NAME") 45 | ticker_col = headers.index("TICKER") 46 | pos_units_col = headers.index("POSITION UNITS") 47 | portfolio_col = headers.index("PORTFOLIO") 48 | price_col = headers.index("PRICE") 49 | currency_col = headers.index("CURRENCY") 50 | 51 | #for d in data... 52 | for d in data[1:]: 53 | hldg = { 54 | "name":d[name_col], 55 | "instrumentId":d[unique_id_col], 56 | "quantity":d[pos_units_col] 57 | } 58 | if len(headers)>5: 59 | for meta in headers[6:]: 60 | hldg[meta.replace('\r','')] = d[headers.index(meta)].replace('\r','') 61 | 62 | if d[portfolio_col] not in portfolios: 63 | portfolios[d[portfolio_col]] = [hldg] 64 | else: 65 | portfolios[d[portfolio_col]].append(hldg) 66 | 67 | #Send each portfolio and its holdings to the investment portfolio service 68 | for key, value in portfolios.items(): 69 | if key == 'User Portfolio' or key == 'Sample Portfolio': 70 | portfolio_type = 'User Portfolio' 71 | else: 72 | portfolio_type = 'look through portfolio' 73 | 74 | my_portfolio = { 75 | "timestamp": '{:%Y-%m-%dT%H:%M:%S.%fZ}'.format(datetime.datetime.now()) , 76 | 'closed':False, 77 | 'data':{'type':portfolio_type}, 78 | 'name':key 79 | } 80 | #Don't show look-through portfolios in main drop-down menu 81 | 82 | #create portfolio 83 | try: 84 | req = investmentportfolio.Create_Portfolio(my_portfolio) 85 | except: 86 | print("Unable to create portfolio for " + str(key) + ".") 87 | 88 | try: 89 | for h in range(0,len(value),100): 90 | hldgs = value[h:h+100] 91 | req = investmentportfolio.Create_Portfolio_Holdings(str(key),hldgs) 92 | except: 93 | print("Unable to create portfolio holdings for " + str(key) + ".") 94 | return req 95 | 96 | 97 | #Returns list of 'look through' portfolios 98 | @app.route('/api/look_through_portfolios',methods=['GET']) 99 | def get_look_through_portfolios(): 100 | ''' 101 | Returns the available user portfolio names in the Investment Portfolio service. 102 | Uses type='user_portfolio' to specify. 103 | ''' 104 | portfolio_names = [] 105 | res = investmentportfolio.Get_Portfolios_by_Selector('type','User Portfolio') #Filters out look-through portfolios 106 | try: 107 | for portfolios in res['portfolios']: 108 | portfolio_names.append(portfolios['name']) 109 | #returns the portfolio names as list 110 | return Response(json.dumps(portfolio_names), mimetype='application/json') 111 | except: 112 | return "No portfolios found." 113 | 114 | #Deletes all look through holdings and portfolios for cleanup 115 | @app.route('/api/look_through_delete',methods=['GET']) 116 | def get_look_through_delete(): 117 | ''' 118 | Deletes all portfolios and respective holdings that are of type 'look through' 119 | ''' 120 | portfolios = investmentportfolio.Get_Portfolios_by_Selector('type','look through portfolio')['portfolios'] 121 | for p in portfolios: 122 | holdings = investmentportfolio.Get_Portfolio_Holdings(p['name'],False) 123 | # delete all holdings 124 | for h in holdings['holdings']: 125 | timestamp = h['timestamp'] 126 | rev = h['_rev'] 127 | investmentportfolio.Delete_Portfolio_Holdings(p['name'],timestamp,rev) 128 | investmentportfolio.Delete_Portfolio(p['name'],p['timestamp'],p['_rev']) 129 | return "Portfolios deleted successfully." 130 | 131 | #======================================LOOK THROUGH CALCULATIONS============================== 132 | #Returns list of 'look through' portfolios with the additional portfolio 133 | def get_universe(portfolio): 134 | #portfolio object as input 135 | universe = portfolio 136 | look_throughs = [item["TICKER"] for item in portfolio if item["HAS_LOOKTHROUGH"] == 'TRUE'] 137 | 138 | for l in look_throughs: 139 | #Get fund's individual holdings 140 | fund = investmentportfolio.Get_Portfolio_Holdings(l,False)['holdings'] 141 | fund = [item['holdings'] for item in fund] #since we loaded the data in chunks originally 142 | fund = [item for sublist in fund for item in sublist] #flatten the list 143 | universe += [item for item in fund if item['TICKER'] != ''] 144 | 145 | return universe 146 | 147 | #Returns an augmented universe with effective portfolio value per security (in case there's a significant difference in processing time with the above) 148 | def get_expanded_universe(portfolio,portfolio_NAV): 149 | #portfolio object as input 150 | universe = portfolio 151 | for p in portfolio: 152 | p.update({'portfolio_value':float(p['quantity'])*float(p['PRICE']),'user_portfolio':True}) 153 | 154 | look_throughs = [item["TICKER"] for item in portfolio if item["HAS_LOOKTHROUGH"] == 'TRUE'] 155 | for l in look_throughs: 156 | #Get fund's NAV from user portfolio (that's where the data lives) and our exposure to that fund (in $) 157 | fund_NAV = [float(item['FUND_NAV']) for item in portfolio if item['TICKER'] == l][0] 158 | exposure_to_fund = ([float(item['quantity']) * float(item['PRICE']) for item in portfolio if item['TICKER'] == l][0])/portfolio_NAV 159 | 160 | #Get fund's individual holdings 161 | fund = investmentportfolio.Get_Portfolio_Holdings(l,False)['holdings'] 162 | fund = [item['holdings'] for item in fund] #since we loaded the data in chunks originally 163 | fund = [item for sublist in fund for item in sublist] #flatten the list 164 | 165 | #calculate effective dollar exposure to each fund based on market value in parent portfolio 166 | for f in fund: 167 | #errors in csv file formats can cause issues here 168 | if f['HAS_LOOKTHROUGH'] == 'FALSE': 169 | try: 170 | f.update({'portfolio_value':((float(f['quantity']) *float(f['PRICE']))/ fund_NAV) * (exposure_to_fund*portfolio_NAV),'user_portfolio':False}) 171 | except: 172 | print('look at ' + str(f['name'])) 173 | universe += fund 174 | return universe 175 | 176 | @app.route('/api/search-universe/',methods=['GET','POST']) 177 | def search_universe(portfolio): 178 | ''' 179 | Returns the total list of securities touched by an investment portfolio (e.g. including look-throughs). 180 | ''' 181 | if request.method == 'POST': 182 | req = request.get_json(silent=True) 183 | portfolio = req['portfolio'] 184 | 185 | portfolio = investmentportfolio.Get_Portfolio_Holdings(portfolio,False)['holdings'] # client portfolio 186 | portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally 187 | portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' 188 | 189 | universe = get_universe(portfolio) 190 | universe = [item['name'] + ' (' + item['TICKER'] + ')' for item in universe] 191 | 192 | return Response(json.dumps(universe), mimetype='application/json') 193 | 194 | @app.route('/api/portfolio-composition',methods=['POST']) 195 | def portfolio_composition(): 196 | ''' 197 | Returns a list of aggregations (e.g. geography, sector) and their portfolio value per member currently in dollar value terms. 198 | ''' 199 | if request.method == 'POST': 200 | req = request.get_json(silent=True) 201 | portfolio = investmentportfolio.Get_Portfolio_Holdings(req['portfolio'],False)['holdings'] # client portfolio 202 | portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally 203 | portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' 204 | aggregations = req["aggregations"] # aggregations to compute 205 | 206 | NAV = sum(float(item['quantity'])*float(item['PRICE']) for item in portfolio) 207 | universe = get_expanded_universe(portfolio) 208 | exposures = {"NAV":NAV} 209 | for a in aggregations: 210 | values = {} 211 | #get unique entries for the given aggregation (keep an eye out for python2 --> 3 quirks) 212 | unique_a = {item[a]:item[a] for item in universe}.values() 213 | for u in unique_a: 214 | values[u] = sum([item['portfolio_value'] for item in universe if item[a]==u]) 215 | exposures[a] = values 216 | 217 | return Response(json.dumps(exposures), mimetype='application/json') 218 | 219 | @app.route('/api/portfolio-analyze/',methods=['GET','POST']) 220 | def portfolio_analyze(portfolio): 221 | ''' 222 | Returns data compatible with the Portfolio.Analyze() v1.0 front-end GUI 223 | ''' 224 | if request.method == 'POST': 225 | req = request.get_json(silent=True) 226 | portfolio = req['portfolio'] 227 | 228 | portfolio_name = portfolio #persist name 229 | portfolio = investmentportfolio.Get_Portfolio_Holdings(portfolio,False)['holdings'] # client portfolio 230 | portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally 231 | portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' 232 | aggregations = ["geography","Asset Class","sector","has_Tobacco","has_Alcohol","has_Gambling","has_Military","has_Fossil Fuels","esg_Controversy","esg_Environmental","esg_Governance","esg_Social","esg_Sustainability"] 233 | 234 | NAV = sum(float(item['quantity'])*float(item['PRICE']) for item in portfolio) 235 | response = { 236 | "NAV":NAV, 237 | 'sin':{}, 238 | 'esg':{portfolio_name:{}}, 239 | 'search':[], # search universe 240 | 'portfolio':[{'name':item['name'],'value ($USD)':(float(item['quantity'])*float(item['PRICE'])),'Portfolio Contribution (%)':((float(item['quantity'])*float(item['PRICE']))/NAV)*100,'Industry Sector':item['sector'],'Asset Class':item['Asset Class'],'Geography':item['geography']} for item in portfolio], 241 | 'composition':{} 242 | } 243 | universe = get_expanded_universe(portfolio,NAV) 244 | response['search'] = list(set([item['name'] + ' (' + item['TICKER'] + ')' for item in universe])) 245 | 246 | #hard-coded benchmarks for now, as it's possible a user would want to make benchmark choices static... 247 | #benchmarks = ['IVV','HYG','LQD'] 248 | benchmarks = ['IVV'] 249 | for b in benchmarks: 250 | response['esg'][b] = {} 251 | 252 | #Calculate data for response 253 | for a in aggregations: 254 | #sin stocks - just need true 255 | if 'has_' in a: 256 | #we omit the parent funds in the portfolio (has_lookthrough=true) to avoid double counting the exposure 257 | response['sin'][a] = sum([item['portfolio_value'] for item in universe if item['HAS_LOOKTHROUGH']=='FALSE' if item[a]=='TRUE']) 258 | #esg 259 | elif 'esg_' in a: 260 | #compute average ESG for the portfolio (and benchmarks!) 261 | response['esg'][portfolio_name][a] = sum([(item['portfolio_value']/NAV)*float(item[a]) for item in universe if item['HAS_LOOKTHROUGH']=='FALSE']) 262 | #regular aggregations 263 | else: 264 | values = {} 265 | #get unique entries for the given aggregation (keep an eye out for python3 quirks) 266 | unique_a = {item[a]:item[a] for item in universe}.values() 267 | for u in unique_a: 268 | values[u] = sum([item['portfolio_value'] for item in universe if item['HAS_LOOKTHROUGH']=='FALSE' if item[a]==u]) 269 | response['composition'][a] = values 270 | 271 | #get ESG data for benchmarks 272 | for b in benchmarks: 273 | portfolio = investmentportfolio.Get_Portfolio_Holdings(b,False)['holdings'] 274 | portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally 275 | portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' 276 | b_NAV = sum(float(item['quantity'])*float(item['PRICE']) for item in portfolio) 277 | b_universe = get_expanded_universe(portfolio,b_NAV) 278 | for a in aggregations: 279 | if 'esg_' in a: 280 | #compute average ESG for the portfolio (and benchmarks!) 281 | response['esg'][b][a] = sum([(item['portfolio_value']/b_NAV)*float(item[a]) for item in b_universe if item['HAS_LOOKTHROUGH']=='FALSE']) 282 | #create world investment json for the D3 element 283 | create_world_json(response['composition']["geography"]) 284 | 285 | return Response(json.dumps(response), mimetype='application/json') 286 | 287 | 288 | #Returns list of 'look through' portfolios (returns results) 289 | @app.route('/api/search//',methods=['GET','POST']) 290 | def search(portfolio,security): 291 | ''' 292 | Returns details around the true presence of a given security [by ticker for now] in a portfolio. 293 | ''' 294 | if request.method == 'POST': 295 | req = request.get_json(silent=True) 296 | portfolio = req["portfolio"] 297 | security = req["security"] # security to check 298 | 299 | portfolio = investmentportfolio.Get_Portfolio_Holdings(portfolio,False)['holdings'] # client portfolio 300 | portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally 301 | portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' 302 | 303 | NAV = sum(float(item['quantity'])*float(item['PRICE']) for item in portfolio) 304 | universe = get_expanded_universe(portfolio,NAV) 305 | exposures = {"NAV":NAV} 306 | #get unique entries for the given aggregation (keep an eye out for python3 quirks) 307 | securities = [item for item in universe if item['TICKER']==security] 308 | #get esg data from the first instance (since they theoretically should all be the same for the same security) 309 | esg_data = {} 310 | price = sum(float(item['portfolio_value']) for item in securities) 311 | for key,value in securities[0].items(): 312 | if 'esg_' in key: 313 | esg_data[key] = value 314 | 315 | exposures = { 316 | "NAV":NAV, 317 | "security":security, 318 | "price": price, 319 | "direct":sum([item['portfolio_value'] for item in securities if item['user_portfolio'] == True]), 320 | "indirect":sum([item['portfolio_value'] for item in securities if item['user_portfolio'] == False]), 321 | "esg":esg_data 322 | } 323 | return Response(json.dumps(exposures), mimetype='application/json') 324 | 325 | 326 | def create_world_json(data): 327 | 328 | if os.path.exists('static/js/geography/world_investment.json'): 329 | os.remove('static/js/geography/world_investment.json') 330 | 331 | with open('static/js/geography/world_investment_default.json','r') as inFile: 332 | jsonData = json.load(inFile) 333 | inFile.close() 334 | 335 | dataLength = len(jsonData) 336 | 337 | for x in range(0, dataLength): 338 | for key in data: 339 | if (jsonData[x]["name"] == key): 340 | jsonData[x]["investments"] = data[key] 341 | 342 | with open('static/js/geography/world_investment.json','w') as outfile: 343 | json.dump(jsonData, outfile) 344 | outfile.close() 345 | 346 | if __name__ == '__main__': 347 | app.run(host=host, port=port) 348 | -------------------------------------------------------------------------------- /static/js/geography/world_investment_default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "CHN", 4 | "name": "China", 5 | "investments": "0" 6 | }, 7 | { 8 | "id": "IND", 9 | "name": "India", 10 | "investments": "0" 11 | }, 12 | { 13 | "id": "USA", 14 | "name": "United States", 15 | "investments": "0" 16 | }, 17 | { 18 | "id": "IDN", 19 | "name": "Indonesia", 20 | "investments": "0" 21 | }, 22 | { 23 | "id": "BRA", 24 | "name": "Brazil", 25 | "investments": "0" 26 | }, 27 | { 28 | "id": "PAK", 29 | "name": "Pakistan", 30 | "investments": "0" 31 | }, 32 | { 33 | "id": "BGD", 34 | "name": "Bangladesh", 35 | "investments": "0" 36 | }, 37 | { 38 | "id": "NGA", 39 | "name": "Nigeria", 40 | "investments": "0" 41 | }, 42 | { 43 | "id": "RUS", 44 | "name": "Russia", 45 | "investments": "0" 46 | }, 47 | { 48 | "id": "JPN", 49 | "name": "Japan", 50 | "investments": "0" 51 | }, 52 | { 53 | "id": "MEX", 54 | "name": "Mexico", 55 | "investments": "0" 56 | }, 57 | { 58 | "id": "PHL", 59 | "name": "Philippines", 60 | "investments": "0" 61 | }, 62 | { 63 | "id": "VNM", 64 | "name": "Vietnam", 65 | "investments": "0" 66 | }, 67 | { 68 | "id": "ETH", 69 | "name": "Ethiopia", 70 | "investments": "0" 71 | }, 72 | { 73 | "id": "DEU", 74 | "name": "Germany", 75 | "investments": "0" 76 | }, 77 | { 78 | "id": "EGY", 79 | "name": "Egypt", 80 | "investments": "0" 81 | }, 82 | { 83 | "id": "TUR", 84 | "name": "Turkey", 85 | "investments": "0" 86 | }, 87 | { 88 | "id": "COD", 89 | "name": "Congo, Democratic Republic of the", 90 | "investments": "0" 91 | }, 92 | { 93 | "id": "IRN", 94 | "name": "Iran", 95 | "investments": "0" 96 | }, 97 | { 98 | "id": "THA", 99 | "name": "Thailand", 100 | "investments": "0" 101 | }, 102 | { 103 | "id": "FRA", 104 | "name": "France", 105 | "investments": "0" 106 | }, 107 | { 108 | "id": "GBR", 109 | "name": "United Kingdom", 110 | "investments": "0" 111 | }, 112 | { 113 | "id": "ITA", 114 | "name": "Italy", 115 | "investments": "0" 116 | }, 117 | { 118 | "id": "MMR", 119 | "name": "Burma", 120 | "investments": "0" 121 | }, 122 | { 123 | "id": "ZAF", 124 | "name": "South Africa", 125 | "investments": "0" 126 | }, 127 | { 128 | "id": "KOR", 129 | "name": "Korea, South", 130 | "investments": "0" 131 | }, 132 | { 133 | "id": "UKR", 134 | "name": "Ukraine", 135 | "investments": "0" 136 | }, 137 | { 138 | "id": "COL", 139 | "name": "Colombia", 140 | "investments": "0" 141 | }, 142 | { 143 | "id": "SDN", 144 | "name": "Sudan", 145 | "investments": "0" 146 | }, 147 | { 148 | "id": "TZA", 149 | "name": "Tanzania", 150 | "investments": "0" 151 | }, 152 | { 153 | "id": "ARG", 154 | "name": "Argentina", 155 | "investments": "0" 156 | }, 157 | { 158 | "id": "ESP", 159 | "name": "Spain", 160 | "investments": "0" 161 | }, 162 | { 163 | "id": "KEN", 164 | "name": "Kenya", 165 | "investments": "0" 166 | }, 167 | { 168 | "id": "POL", 169 | "name": "Poland", 170 | "investments": "0" 171 | }, 172 | { 173 | "id": "DZA", 174 | "name": "Algeria", 175 | "investments": "0" 176 | }, 177 | { 178 | "id": "CAN", 179 | "name": "Canada", 180 | "investments": "0" 181 | }, 182 | { 183 | "id": "UGA", 184 | "name": "Uganda", 185 | "investments": "0" 186 | }, 187 | { 188 | "id": "MAR", 189 | "name": "Morocco", 190 | "investments": "0" 191 | }, 192 | { 193 | "id": "PER", 194 | "name": "Peru", 195 | "investments": "0" 196 | }, 197 | { 198 | "id": "IRQ", 199 | "name": "Iraq", 200 | "investments": "0" 201 | }, 202 | { 203 | "id": "SAU", 204 | "name": "Saudi Arabia", 205 | "investments": "0" 206 | }, 207 | { 208 | "id": "AFG", 209 | "name": "Afghanistan", 210 | "investments": "0" 211 | }, 212 | { 213 | "id": "NPL", 214 | "name": "Nepal", 215 | "investments": "0" 216 | }, 217 | { 218 | "id": "UZB", 219 | "name": "Uzbekistan", 220 | "investments": "0" 221 | }, 222 | { 223 | "id": "VEN", 224 | "name": "Venezuela", 225 | "investments": "0" 226 | }, 227 | { 228 | "id": "MYS", 229 | "name": "Malaysia", 230 | "investments": "0" 231 | }, 232 | { 233 | "id": "GHA", 234 | "name": "Ghana", 235 | "investments": "0" 236 | }, 237 | { 238 | "id": "YEM", 239 | "name": "Yemen", 240 | "investments": "0" 241 | }, 242 | { 243 | "id": "TWN", 244 | "name": "Taiwan", 245 | "investments": "0" 246 | }, 247 | { 248 | "id": "PRK", 249 | "name": "Korea, North", 250 | "investments": "0" 251 | }, 252 | { 253 | "id": "SYR", 254 | "name": "Syria", 255 | "investments": "0" 256 | }, 257 | { 258 | "id": "ROU", 259 | "name": "Romania", 260 | "investments": "0" 261 | }, 262 | { 263 | "id": "MOZ", 264 | "name": "Mozambique", 265 | "investments": "0" 266 | }, 267 | { 268 | "id": "AUS", 269 | "name": "Australia", 270 | "investments": "0" 271 | }, 272 | { 273 | "id": "LKA", 274 | "name": "Sri Lanka", 275 | "investments": "0" 276 | }, 277 | { 278 | "id": "MDG", 279 | "name": "Madagascar", 280 | "investments": "0" 281 | }, 282 | { 283 | "id": "CIV", 284 | "name": "Cote d'Ivoire", 285 | "investments": "0" 286 | }, 287 | { 288 | "id": "CMR", 289 | "name": "Cameroon", 290 | "investments": "0" 291 | }, 292 | { 293 | "id": "NLD", 294 | "name": "Netherlands", 295 | "investments": "0" 296 | }, 297 | { 298 | "id": "CHL", 299 | "name": "Chile", 300 | "investments": "0" 301 | }, 302 | { 303 | "id": "BFA", 304 | "name": "Burkina Faso", 305 | "investments": "0" 306 | }, 307 | { 308 | "id": "NER", 309 | "name": "Niger", 310 | "investments": "0" 311 | }, 312 | { 313 | "id": "KAZ", 314 | "name": "Kazakhstan", 315 | "investments": "0" 316 | }, 317 | { 318 | "id": "MWI", 319 | "name": "Malawi", 320 | "investments": "0" 321 | }, 322 | { 323 | "id": "ECU", 324 | "name": "Ecuador", 325 | "investments": "0" 326 | }, 327 | { 328 | "id": "KHM", 329 | "name": "Cambodia", 330 | "investments": "0" 331 | }, 332 | { 333 | "id": "SEN", 334 | "name": "Senegal", 335 | "investments": "0" 336 | }, 337 | { 338 | "id": "MLI", 339 | "name": "Mali", 340 | "investments": "0" 341 | }, 342 | { 343 | "id": "GTM", 344 | "name": "Guatemala", 345 | "investments": "0" 346 | }, 347 | { 348 | "id": "AGO", 349 | "name": "Angola", 350 | "investments": "0" 351 | }, 352 | { 353 | "id": "ZMB", 354 | "name": "Zambia", 355 | "investments": "0" 356 | }, 357 | { 358 | "id": "ZWE", 359 | "name": "Zimbabwe", 360 | "investments": "0" 361 | }, 362 | { 363 | "id": "CUB", 364 | "name": "Cuba", 365 | "investments": "0" 366 | }, 367 | { 368 | "id": "RWA", 369 | "name": "Rwanda", 370 | "investments": "0" 371 | }, 372 | { 373 | "id": "GRC", 374 | "name": "Greece", 375 | "investments": "0" 376 | }, 377 | { 378 | "id": "PRT", 379 | "name": "Portugal", 380 | "investments": "0" 381 | }, 382 | { 383 | "id": "TUN", 384 | "name": "Tunisia", 385 | "investments": "0" 386 | }, 387 | { 388 | "id": "TCD", 389 | "name": "Chad", 390 | "investments": "0" 391 | }, 392 | { 393 | "id": "BEL", 394 | "name": "Belgium", 395 | "investments": "0" 396 | }, 397 | { 398 | "id": "GIN", 399 | "name": "Guinea", 400 | "investments": "0" 401 | }, 402 | { 403 | "id": "CZE", 404 | "name": "Czech Republic", 405 | "investments": "0" 406 | }, 407 | { 408 | "id": "SOM", 409 | "name": "Somalia", 410 | "investments": "0" 411 | }, 412 | { 413 | "id": "BOL", 414 | "name": "Bolivia", 415 | "investments": "0" 416 | }, 417 | { 418 | "id": "HUN", 419 | "name": "Hungary", 420 | "investments": "0" 421 | }, 422 | { 423 | "id": "BDI", 424 | "name": "Burundi", 425 | "investments": "0" 426 | }, 427 | { 428 | "id": "DOM", 429 | "name": "Dominican Republic", 430 | "investments": "0" 431 | }, 432 | { 433 | "id": "BLR", 434 | "name": "Belarus", 435 | "investments": "0" 436 | }, 437 | { 438 | "id": "HTI", 439 | "name": "Haiti", 440 | "investments": "0" 441 | }, 442 | { 443 | "id": "SWE", 444 | "name": "Sweden", 445 | "investments": "0" 446 | }, 447 | { 448 | "id": "BEN", 449 | "name": "Benin", 450 | "investments": "0" 451 | }, 452 | { 453 | "id": "AZE", 454 | "name": "Azerbaijan", 455 | "investments": "0" 456 | }, 457 | { 458 | "id": "AUT", 459 | "name": "Austria", 460 | "investments": "0" 461 | }, 462 | { 463 | "id": "HND", 464 | "name": "Honduras", 465 | "investments": "0" 466 | }, 467 | { 468 | "id": "CHE", 469 | "name": "Switzerland", 470 | "investments": "0" 471 | }, 472 | { 473 | "id": "TJK", 474 | "name": "Tajikistan", 475 | "investments": "0" 476 | }, 477 | { 478 | "id": "ISR", 479 | "name": "Israel", 480 | "investments": "0" 481 | }, 482 | { 483 | "id": "SRB", 484 | "name": "Serbia", 485 | "investments": "0" 486 | }, 487 | { 488 | "id": "BGR", 489 | "name": "Bulgaria", 490 | "investments": "0" 491 | }, 492 | { 493 | "id": "HKG", 494 | "name": "Hong Kong", 495 | "investments": "0" 496 | }, 497 | { 498 | "id": "LAO", 499 | "name": "Laos", 500 | "investments": "0" 501 | }, 502 | { 503 | "id": "LBY", 504 | "name": "Libya", 505 | "investments": "0" 506 | }, 507 | { 508 | "id": "JOR", 509 | "name": "Jordan", 510 | "investments": "0" 511 | }, 512 | { 513 | "id": "PRY", 514 | "name": "Paraguay", 515 | "investments": "0" 516 | }, 517 | { 518 | "id": "TGO", 519 | "name": "Togo", 520 | "investments": "0" 521 | }, 522 | { 523 | "id": "PNG", 524 | "name": "Papua New Guinea", 525 | "investments": "0" 526 | }, 527 | { 528 | "id": "SLV", 529 | "name": "El Salvador", 530 | "investments": "0" 531 | }, 532 | { 533 | "id": "NIC", 534 | "name": "Nicaragua", 535 | "investments": "0" 536 | }, 537 | { 538 | "id": "ERI", 539 | "name": "Eritrea", 540 | "investments": "0" 541 | }, 542 | { 543 | "id": "DNK", 544 | "name": "Denmark", 545 | "investments": "0" 546 | }, 547 | { 548 | "id": "KGZ", 549 | "name": "Kyrgyzstan", 550 | "investments": "0" 551 | }, 552 | { 553 | "id": "SVK", 554 | "name": "Slovakia", 555 | "investments": "0" 556 | }, 557 | { 558 | "id": "FIN", 559 | "name": "Finland", 560 | "investments": "0" 561 | }, 562 | { 563 | "id": "SLE", 564 | "name": "Sierra Leone", 565 | "investments": "0" 566 | }, 567 | { 568 | "id": "ARE", 569 | "name": "United Arab Emirates", 570 | "investments": "0" 571 | }, 572 | { 573 | "id": "TKM", 574 | "name": "Turkmenistan", 575 | "investments": "0" 576 | }, 577 | { 578 | "id": "CAF", 579 | "name": "Central African Republic", 580 | "investments": "0" 581 | }, 582 | { 583 | "id": "SGP", 584 | "name": "Singapore", 585 | "investments": "0" 586 | }, 587 | { 588 | "id": "NOR", 589 | "name": "Norway", 590 | "investments": "0" 591 | }, 592 | { 593 | "id": "BIH", 594 | "name": "Bosnia and Herzegovina", 595 | "investments": "0" 596 | }, 597 | { 598 | "id": "GEO", 599 | "name": "Georgia", 600 | "investments": "0" 601 | }, 602 | { 603 | "id": "CRI", 604 | "name": "Costa Rica", 605 | "investments": "0" 606 | }, 607 | { 608 | "id": "HRV", 609 | "name": "Croatia", 610 | "investments": "0" 611 | }, 612 | { 613 | "id": "MDA", 614 | "name": "Moldova", 615 | "investments": "0" 616 | }, 617 | { 618 | "id": "NZL", 619 | "name": "New Zealand", 620 | "investments": "0" 621 | }, 622 | { 623 | "id": "IRL", 624 | "name": "Ireland", 625 | "investments": "0" 626 | }, 627 | { 628 | "id": "COG", 629 | "name": "Congo, Republic of the", 630 | "investments": "0" 631 | }, 632 | { 633 | "id": "LBN", 634 | "name": "Lebanon", 635 | "investments": "0" 636 | }, 637 | { 638 | "id": "PRI", 639 | "name": "Puerto Rico", 640 | "investments": "0" 641 | }, 642 | { 643 | "id": "LBR", 644 | "name": "Liberia", 645 | "investments": "0" 646 | }, 647 | { 648 | "id": "ALB", 649 | "name": "Albania", 650 | "investments": "0" 651 | }, 652 | { 653 | "id": "LTU", 654 | "name": "Lithuania", 655 | "investments": "0" 656 | }, 657 | { 658 | "id": "URY", 659 | "name": "Uruguay", 660 | "investments": "0" 661 | }, 662 | { 663 | "id": "PAN", 664 | "name": "Panama", 665 | "investments": "0" 666 | }, 667 | { 668 | "id": "MRT", 669 | "name": "Mauritania", 670 | "investments": "0" 671 | }, 672 | { 673 | "id": "MNG", 674 | "name": "Mongolia", 675 | "investments": "0" 676 | }, 677 | { 678 | "id": "OMN", 679 | "name": "Oman", 680 | "investments": "0" 681 | }, 682 | { 683 | "id": "ARM", 684 | "name": "Armenia", 685 | "investments": "0" 686 | }, 687 | { 688 | "id": "JAM", 689 | "name": "Jamaica", 690 | "investments": "0" 691 | }, 692 | { 693 | "id": "KWT", 694 | "name": "Kuwait", 695 | "investments": "0" 696 | }, 697 | { 698 | "id": "PSE", 699 | "name": "West Bank", 700 | "investments": "0" 701 | }, 702 | { 703 | "id": "LVA", 704 | "name": "Latvia", 705 | "investments": "0" 706 | }, 707 | { 708 | "id": "NAM", 709 | "name": "Namibia", 710 | "investments": "0" 711 | }, 712 | { 713 | "id": "MKD", 714 | "name": "Macedonia", 715 | "investments": "0" 716 | }, 717 | { 718 | "id": "BWA", 719 | "name": "Botswana", 720 | "investments": "0" 721 | }, 722 | { 723 | "id": "SVN", 724 | "name": "Slovenia", 725 | "investments": "0" 726 | }, 727 | { 728 | "id": "LSO", 729 | "name": "Lesotho", 730 | "investments": "0" 731 | }, 732 | { 733 | "id": "GMB", 734 | "name": "Gambia, The", 735 | "investments": "0" 736 | }, 737 | { 738 | "id": "KWT", 739 | "name": "Kosovo", 740 | "investments": "0" 741 | }, 742 | { 743 | "id": "149", 744 | "name": "Gaza Strip", 745 | "investments": "0" 746 | }, 747 | { 748 | "id": "GNB", 749 | "name": "Guinea-Bissau", 750 | "investments": "0" 751 | }, 752 | { 753 | "id": "GAB", 754 | "name": "Gabon", 755 | "investments": "0" 756 | }, 757 | { 758 | "id": "SWZ", 759 | "name": "Swaziland", 760 | "investments": "0" 761 | }, 762 | { 763 | "id": "153", 764 | "name": "Mauritius", 765 | "investments": "0" 766 | }, 767 | { 768 | "id": "EST", 769 | "name": "Estonia", 770 | "investments": "0" 771 | }, 772 | { 773 | "id": "TTO", 774 | "name": "Trinidad and Tobago", 775 | "investments": "0" 776 | }, 777 | { 778 | "id": "TLS", 779 | "name": "Timor-Leste", 780 | "investments": "0" 781 | }, 782 | { 783 | "id": "CYP", 784 | "name": "Cyprus", 785 | "investments": "0" 786 | }, 787 | { 788 | "id": "FJI", 789 | "name": "Fiji", 790 | "investments": "0" 791 | }, 792 | { 793 | "id": "QAT", 794 | "name": "Qatar", 795 | "investments": "0" 796 | }, 797 | { 798 | "id": "160", 799 | "name": "Comoros", 800 | "investments": "0" 801 | }, 802 | { 803 | "id": "GUY", 804 | "name": "Guyana", 805 | "investments": "0" 806 | }, 807 | { 808 | "id": "DJI", 809 | "name": "Djibouti", 810 | "investments": "0" 811 | }, 812 | { 813 | "id": "163", 814 | "name": "Bahrain", 815 | "investments": "0" 816 | }, 817 | { 818 | "id": "BTN", 819 | "name": "Bhutan", 820 | "investments": "0" 821 | }, 822 | { 823 | "id": "MNE", 824 | "name": "Montenegro", 825 | "investments": "0" 826 | }, 827 | { 828 | "id": "GNQ", 829 | "name": "Equatorial Guinea", 830 | "investments": "0" 831 | }, 832 | { 833 | "id": "SLB", 834 | "name": "Solomon Islands", 835 | "investments": "0" 836 | }, 837 | { 838 | "id": "168", 839 | "name": "Macau", 840 | "investments": "0" 841 | }, 842 | { 843 | "id": "169", 844 | "name": "Cape Verde", 845 | "investments": "0" 846 | }, 847 | { 848 | "id": "LUX", 849 | "name": "Luxembourg", 850 | "investments": "0" 851 | }, 852 | { 853 | "id": "ESH", 854 | "name": "Western Sahara", 855 | "investments": "0" 856 | }, 857 | { 858 | "id": "SUR", 859 | "name": "Suriname", 860 | "investments": "0" 861 | }, 862 | { 863 | "id": "173", 864 | "name": "Malta", 865 | "investments": "0" 866 | }, 867 | { 868 | "id": "174", 869 | "name": "Maldives", 870 | "investments": "0" 871 | }, 872 | { 873 | "id": "BRN", 874 | "name": "Brunei", 875 | "investments": "0" 876 | }, 877 | { 878 | "id": "BLZ", 879 | "name": "Belize", 880 | "investments": "0" 881 | }, 882 | { 883 | "id": "BHS", 884 | "name": "Bahamas, The", 885 | "investments": "0" 886 | }, 887 | { 888 | "id": "ISL", 889 | "name": "Iceland", 890 | "investments": "0" 891 | }, 892 | { 893 | "id": "179", 894 | "name": "French Polynesia", 895 | "investments": "0" 896 | }, 897 | { 898 | "id": "180", 899 | "name": "Barbados", 900 | "investments": "0" 901 | }, 902 | { 903 | "id": "181", 904 | "name": "Mayotte", 905 | "investments": "0" 906 | }, 907 | { 908 | "id": "NCL", 909 | "name": "New Caledonia", 910 | "investments": "0" 911 | }, 912 | { 913 | "id": "183", 914 | "name": "Netherlands Antilles", 915 | "investments": "0" 916 | }, 917 | { 918 | "id": "VUT", 919 | "name": "Vanuatu", 920 | "investments": "0" 921 | }, 922 | { 923 | "id": "185", 924 | "name": "Samoa", 925 | "investments": "0" 926 | }, 927 | { 928 | "id": "186", 929 | "name": "Sao Tome and Principe", 930 | "investments": "0" 931 | }, 932 | { 933 | "id": "187", 934 | "name": "Saint Lucia", 935 | "investments": "0" 936 | }, 937 | { 938 | "id": "188", 939 | "name": "Tonga", 940 | "investments": "0" 941 | }, 942 | { 943 | "id": "189", 944 | "name": "Virgin Islands", 945 | "investments": "0" 946 | }, 947 | { 948 | "id": "190", 949 | "name": "Grenada", 950 | "investments": "0" 951 | }, 952 | { 953 | "id": "191", 954 | "name": "Micronesia, Federated States of", 955 | "investments": "0" 956 | }, 957 | { 958 | "id": "192", 959 | "name": "Aruba", 960 | "investments": "0" 961 | }, 962 | { 963 | "id": "193", 964 | "name": "Saint Vincent and the Grenadines", 965 | "investments": "0" 966 | }, 967 | { 968 | "id": "194", 969 | "name": "Kiribati", 970 | "investments": "0" 971 | }, 972 | { 973 | "id": "195", 974 | "name": "Jersey", 975 | "investments": "0" 976 | }, 977 | { 978 | "id": "196", 979 | "name": "Seychelles", 980 | "investments": "0" 981 | }, 982 | { 983 | "id": "197", 984 | "name": "Antigua and Barbuda", 985 | "investments": "0" 986 | }, 987 | { 988 | "id": "198", 989 | "name": "Andorra", 990 | "investments": "0" 991 | }, 992 | { 993 | "id": "199", 994 | "name": "Isle of Man", 995 | "investments": "0" 996 | }, 997 | { 998 | "id": "DOM", 999 | "name": "Dominica", 1000 | "investments": "0" 1001 | }, 1002 | { 1003 | "id": "201", 1004 | "name": "Bermuda", 1005 | "investments": "0" 1006 | }, 1007 | { 1008 | "id": "202", 1009 | "name": "American Samoa", 1010 | "investments": "0" 1011 | }, 1012 | { 1013 | "id": "203", 1014 | "name": "Marshall Islands", 1015 | "investments": "0" 1016 | }, 1017 | { 1018 | "id": "204", 1019 | "name": "Guernsey", 1020 | "investments": "0" 1021 | }, 1022 | { 1023 | "id": "GRL", 1024 | "name": "Greenland", 1025 | "investments": "0" 1026 | }, 1027 | { 1028 | "id": "206", 1029 | "name": "Cayman Islands", 1030 | "investments": "0" 1031 | }, 1032 | { 1033 | "id": "207", 1034 | "name": "Saint Kitts and Nevis", 1035 | "investments": "0" 1036 | }, 1037 | { 1038 | "id": "208", 1039 | "name": "Faroe Islands", 1040 | "investments": "0" 1041 | }, 1042 | { 1043 | "id": "209", 1044 | "name": "Northern Mariana Islands", 1045 | "investments": "0" 1046 | }, 1047 | { 1048 | "id": "210", 1049 | "name": "Liechtenstein", 1050 | "investments": "0" 1051 | }, 1052 | { 1053 | "id": "211", 1054 | "name": "San Marino", 1055 | "investments": "0" 1056 | }, 1057 | { 1058 | "id": "212", 1059 | "name": "Monaco", 1060 | "investments": "0" 1061 | }, 1062 | { 1063 | "id": "213", 1064 | "name": "Saint Martin", 1065 | "investments": "0" 1066 | }, 1067 | { 1068 | "id": "214", 1069 | "name": "Gibraltar", 1070 | "investments": "0" 1071 | }, 1072 | { 1073 | "id": "215", 1074 | "name": "British Virgin Islands", 1075 | "investments": "0" 1076 | }, 1077 | { 1078 | "id": "216", 1079 | "name": "Turks and Caicos Islands", 1080 | "investments": "0" 1081 | }, 1082 | { 1083 | "id": "217", 1084 | "name": "Palau", 1085 | "investments": "0" 1086 | }, 1087 | { 1088 | "id": "218", 1089 | "name": "Akrotiri", 1090 | "investments": "0" 1091 | }, 1092 | { 1093 | "id": "219", 1094 | "name": "Dhekelia", 1095 | "investments": "0" 1096 | }, 1097 | { 1098 | "id": "220", 1099 | "name": "Wallis and Futuna", 1100 | "investments": "0" 1101 | }, 1102 | { 1103 | "id": "221", 1104 | "name": "Anguilla", 1105 | "investments": "0" 1106 | }, 1107 | { 1108 | "id": "222", 1109 | "name": "Nauru", 1110 | "investments": "0" 1111 | }, 1112 | { 1113 | "id": "223", 1114 | "name": "Cook Islands", 1115 | "investments": "0" 1116 | }, 1117 | { 1118 | "id": "224", 1119 | "name": "Tuvalu", 1120 | "investments": "0" 1121 | }, 1122 | { 1123 | "id": "225", 1124 | "name": "Saint Helena, Ascension, and Tristan da Cunha", 1125 | "investments": "0" 1126 | }, 1127 | { 1128 | "id": "226", 1129 | "name": "Saint Barthelemy", 1130 | "investments": "0" 1131 | }, 1132 | { 1133 | "id": "227", 1134 | "name": "Saint Pierre and Miquelon", 1135 | "investments": "0" 1136 | }, 1137 | { 1138 | "id": "228", 1139 | "name": "Montserrat", 1140 | "investments": "0" 1141 | }, 1142 | { 1143 | "id": "FLK", 1144 | "name": "Falkland Islands (Islas Malvinas)", 1145 | "investments": "0" 1146 | }, 1147 | { 1148 | "id": "230", 1149 | "name": "Norfolk Island", 1150 | "investments": "0" 1151 | }, 1152 | { 1153 | "id": "231", 1154 | "name": "Svalbard", 1155 | "investments": "0" 1156 | }, 1157 | { 1158 | "id": "232", 1159 | "name": "Christmas Island", 1160 | "investments": "0" 1161 | }, 1162 | { 1163 | "id": "233", 1164 | "name": "Tokelau", 1165 | "investments": "0" 1166 | }, 1167 | { 1168 | "id": "234", 1169 | "name": "Niue", 1170 | "investments": "0" 1171 | }, 1172 | { 1173 | "id": "235", 1174 | "name": "Holy See (Vatican City)", 1175 | "investments": "829" 1176 | }, 1177 | { 1178 | "id": "236", 1179 | "name": "Cocos (Keeling) Islands", 1180 | "investments": "596" 1181 | }, 1182 | { 1183 | "id": "237", 1184 | "name": "Pitcairn Islands", 1185 | "investments": "48" 1186 | }, 1187 | { 1188 | "id": "ATA", 1189 | "name": "Antarctica", 1190 | "investments": "0" 1191 | }, 1192 | { 1193 | "id": "ATF", 1194 | "name": "French Southern and Antarctic Lands", 1195 | "investments": "0" 1196 | }, 1197 | { 1198 | "id": "SDS", 1199 | "name": "South Sudan", 1200 | "investments": "0" 1201 | }, 1202 | { 1203 | "id": "ABV", 1204 | "name": "Somaliland", 1205 | "investments": "0" 1206 | }, 1207 | { 1208 | "id": "OSA", 1209 | "name": "Kosovo", 1210 | "investments": "0" 1211 | } 1212 | ] 1213 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Portfolio Analyze 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | Portfolio Analyze   31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 | Do you know what's in your portfolio? 41 |
42 | 43 |
44 |
45 |
46 |
47 |

Use our sample portfolios to see how we analyze portfolios and preview the features.

48 | 49 |
50 | 51 |
52 | 57 |
58 |

Note: All data in our sample portfolios is simulated and therefore representative of the actual data.

59 |
60 |
61 | 62 |
63 |
64 |
65 |

Upload your portfolio to find out more about what you are holding and what the possibilities for change are.

66 | 67 | 68 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | loading 81 |

82 | Your analysis will be completed shortly. 83 |

84 |
85 | 86 |
87 |
88 |
89 | 90 |
91 | 92 | 93 | Created with Sketch. 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | 107 |
108 | 109 | 110 | 111 | Created with Sketch. 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
125 | 126 |
127 | 128 |
129 |

Portfolio Composition by 130 | 135 |

136 | 137 |
138 | 139 | 140 | 149 | 150 | 154 | 155 |
141 |
142 | 143 |
144 |

145 |

146 |
147 |
148 |
151 |

Key

152 |
153 |
156 |
157 | 158 |
159 |
160 |
161 | 162 |
163 |
164 |
165 |
166 | 167 | 168 |
169 |
170 | 171 |
172 |
173 | 174 |
175 | 176 |
177 |
178 |
179 |

Portfolio Composition Table

180 |
181 | 182 | 183 | 184 | 185 | 186 |
187 |
188 |
189 | 190 |





191 | 192 |
193 | 194 | 195 |
196 |
197 |

Portfolio Composition by ESG Category

198 | 199 | 200 | 201 | 210 | 211 | 220 | 221 | 230 | 231 | 240 | 241 | 250 | 251 | 252 | 253 | 254 | 257 | 260 | 263 | 266 | 269 | 270 |
202 |
203 | 204 |
205 |

206 |

207 |
208 |
209 |
212 |
213 | 214 |
215 |

216 |

217 |
218 |
219 |
222 |
223 | 224 |
225 |

226 |

227 |
228 |
229 |
232 |
233 | 234 |
235 |

236 |

237 |
238 |
239 |
242 |
243 | 244 |
245 |

246 |

247 |
248 |
249 |
255 |

Sustainability

256 |
258 |

Controversy

259 |
261 |

Environmental

262 |
264 |

Social

265 |
267 |

Governance

268 |
271 | 272 |
273 | 274 | 275 | 325 | 326 |
327 |
328 |

Benchmarks

329 |

How does your portfolio compare to the benchmarks for ESG categories?

330 | 331 | 332 | 333 | 339 | 340 | 344 | 345 |
334 |
335 | 336 |
337 |
338 |
341 |

Key

342 |
343 |
346 |
347 | 348 | 349 |
350 |
351 |

"Sin" Investments In Your Portfolio

352 |
353 | 354 |
355 |
356 | 357 |

"Sin" Exposure

358 | 359 |
360 | 361 |
362 |
363 |


364 |
365 | 366 |
367 |

"Sin" Investments Breakdown

368 | 369 | 370 | 371 | 382 | 383 | 387 | 388 |
372 |
373 | 374 | 378 | 379 |
380 | 381 |
384 |

Key

385 |
386 |
389 | 390 |
391 |
392 | 393 |
394 | 395 |
396 |
397 |

Is socially responsible investment important to you?

398 |
399 |

400 | You’re not alone. In the last 5 years ESG investing has grown exponentially, with available ESG funds exploding in 401 | popularity, diversity of approach and sophistication. Today it is possible to take into consideration environmental, social 402 | and governance issues alongside financial factors in the investment decision-making process. 403 |

404 | 405 |

You can make a difference without hindering performance.

406 |
407 | 408 |
409 | Make a change 410 |
411 |
412 |
413 | 414 |
415 | 416 | 417 | Created with Sketch. 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | Improve 428 | 429 | 430 | 431 | 432 | "Sin" Investments 433 | 434 | 435 | 436 | 437 | Benchmarks 438 | 439 | 440 | 441 | 442 | Search 443 | 444 | 445 | 446 | 447 | ESG Categories 448 | 449 | 450 | 451 | 452 | 453 | Composition 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | Search 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 |
600 |
601 |
602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | --------------------------------------------------------------------------------