Skip to content

Commit

Permalink
Feature/investment screener (#52)
Browse files Browse the repository at this point in the history
* Implemented investment screener.

* Implemented InvestmentScreenerConnector together with the demo.

* Fixed demo and changed the eslint config.

* Implemented investment screener tests.

* Fixed tests.

* Fixed test.

* Fixed linting errors.

* Added investment screener demo to list of demos.

* Fixed typo

* Added docs for Investment Screener.

* Added comments to demo.

* Fixed docs.

* Fix typo

* Update docs/connectors/morningstar/screeners/investment-screener.md

Co-authored-by: Kamil Musiałowski <[email protected]>

* Added console warn on error in demo.

* Added missing typo fixes.

---------

Co-authored-by: Kamil Musiałowski <[email protected]>
Co-authored-by: Kamil Musiałowski <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 608f4f8 commit a9ef98d
Show file tree
Hide file tree
Showing 15 changed files with 983 additions and 8 deletions.
63 changes: 63 additions & 0 deletions demos/dashboards-investment-screener/demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@import url("https://code.highcharts.com/dashboards/css/datagrid.css");
@import url("https://code.highcharts.com/css/highcharts.css");
@import url("https://code.highcharts.com/dashboards/css/dashboards.css");

body {
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
}

.row {
display: flex;
flex-wrap: wrap;
}

.cell {
flex: 1;
min-width: 20px;
}

.cell>.highcharts-dashboards-component {
position: relative;
margin: 10px;
background-clip: border-box;
}

.highcharts-dashboards-component-title {
padding: 10px;
margin: 0;
background-color: var(--highcharts-neutral-color-5);
color: var(--highcharts-neutral-color-100);
border: solid 1px var(--highcharts-neutral-color-20);
border-bottom: none;
}

@media screen and (max-width: 1000px) {
.row {
flex-direction: column;
}
}

.filters-row {
flex-direction: row;
justify-content: space-evenly;
}

#dashboard-col-1 {
height: 500px;
}

.filters-row>button {
background: #f2f2f2;
border: none;
border-radius: 4px;
cursor: pointer;
display: inline-block;
font-size: 0.8rem;
padding: 0.5rem 1.5rem;
margin: 0.5rem -5px 0.5rem 10px;
transition: background 250ms;
}

.filters-row>button:hover {
background: #e6e6e6;
}
38 changes: 38 additions & 0 deletions demos/dashboards-investment-screener/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="demo.css" />
<script src="https://code.highcharts.com/dashboards/datagrid.src.js"></script>
<script src="https://code.highcharts.com/highcharts.src.js"></script>
<script src="https://code.highcharts.com/dashboards/dashboards.src.js"></script>
<script src="../../code/connectors-morningstar.src.js"></script>

<title>Highcharts Dashboards + Morningstar Portfolio Investment Details</title>
</head>
<body>
<h1>Highcharts Dashboards + Morningstar Portfolio Investment Details</h1>
<p>
Add your Postman environment file from Morningstar to start the demo:
<input type="file" id="postman-json" accept=".json,application/json" />
</p>
<p id="loading-label" style="display: none;">Loading data…</p>
<div class="filters-row">
<button class="cell" id='filter-1'>Highly Rated</button>
<button class="cell" id='filter-2'>Top Performer funds</button>
<button class="cell" id='filter-3'>Highly Sustainalble</button>
<button class="cell" id='filter-4'>Low Expenses</button>
</div>
<div>
Current filter:
<span id='current-filter'></span>
</div>
<div class="row" id="container">
<div class="cell" id="dashboard-col-1"></div>
</div>
<span id='total'>-</span>
<span id='page'>-</span>
<span id='total-pages'>-</span>
<script src="./demo.js"></script>
</body>
</html>
219 changes: 219 additions & 0 deletions demos/dashboards-investment-screener/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
const loadingLabel = document.getElementById('loading-label');

function displayInvestmentScreener (postmanJSON) {
const secIds = [
'secId',
'tenforeId',
'name',
'closePrice',
'ongoingCharge',
'initialPurchase',
'maxFrontEndLoad',
'analystRatingScale',
'average12MonthCarbonRiskScore',
'investmentType',
'holdingTypeId',
'universe'
];

const columns = secIds.map(id => ({
id: `InvestmentScreener_${id}`,
header: {
format: id
}
}));

const board = Dashboards.board('container', {
dataPool: {
connectors: [
{
id: 'investment-screener',
type: 'MorningstarInvestmentScreener',
options: {
page: 1,
pageSize: 20,
langageId: 'en-GB',
currencyId: 'USD',
securityDataPoints: secIds,
universeIds: ['FOALL$$ALL'],
postman: {
environmentJSON: postmanJSON
}
}
}
]
},
components: [
{
renderTo: 'dashboard-col-1',
connector: {
id: 'investment-screener'
},
type: 'DataGrid',

dataGridOptions: {
editable: false,
columns
},
title: 'Investment Screener'
}
]
});

board.dataPool.getConnector('investment-screener').then(connector => {
loadingLabel.style.display = 'none';
document.getElementById('total').innerHTML =
`total - ${connector.metadata.total}`;
document.getElementById('page').innerHTML =
`page - ${connector.metadata.page}`;
document.getElementById('total-pages').innerHTML =
`out of ${Math.ceil(connector.metadata.total / connector.metadata.pageSize)}`;
});

/**
* Add filter to a connector
*
* @param {InvestmentScreenerFilter[]} filters
*/
function setFilter (filters) {
loadingLabel.style.display = 'block';
board.dataPool.getConnector('investment-screener').then(connector => {
const options = {
filters
};
connector.load(options).then(() => {
loadingLabel.style.display = 'none';
document.getElementById('total').innerHTML =
`total - ${connector.metadata.total}`;
document.getElementById('page').innerHTML =
`page - ${connector.metadata.page}`;
document.getElementById('total-pages').innerHTML =
`out of ${Math.ceil(connector.metadata.total / connector.metadata.pageSize)}`;
});
});
}

document.getElementById('filter-1').addEventListener('click', e => {
e.target.classList.add('button-active');
document.getElementById('current-filter').innerHTML =
e.target.innerHTML;
// Create a filter that will check if the star rating is equal to 5
// and if the analyst rating is equal to 5
setFilter([
{
dataPointId: 'StarRatingM255',
comparatorCode: 'IN',
value: 5
},
{
dataPointId: 'AnalystRatingScale',
comparatorCode: 'IN',
value: 5
}
]);
});

document.getElementById('filter-2').addEventListener('click', e => {
document.getElementById('current-filter').innerHTML =
e.target.innerHTML;
// Create a filter that will check if the GBR return is between
// 39 and 60
setFilter([
{
dataPointId: 'GBRReturnM0',
comparatorCode: 'BTW',
value: '40:60'
}
]);
});

document.getElementById('filter-3').addEventListener('click', e => {
document.getElementById('current-filter').innerHTML =
e.target.innerHTML;
// Create a filter that will filter on the Low Carbon Designation
// and Carbon Risk Score. These Investments are considered highly
// sustainable
setFilter([
{
dataPointId: 'LowCarbonDesignation',
comparatorCode: 'IN',
value: 'TRUE'
},
{
dataPointId: 'CarbonRiskScore',
comparatorCode: 'EQ',
value: 0
},
{
dataPointId: 'SustainabilityRank',
comparatorCode: 'IN',
value: 5
}
]);
});

document.getElementById('filter-4').addEventListener('click', e => {
document.getElementById('current-filter').innerHTML =
e.target.innerHTML;
// Create a filter that will check if the investment is considered
// as "low expenses"
setFilter([
{
dataPointId: 'StarRatingM255',
comparatorCode: 'IN',
value: 5
},
{ dataPointId: 'OngoingCharge', comparatorCode: 'LT', value: 0.5 },
{
dataPointId: 'initialPurchase',
comparatorCode: 'LT',
value: 500000000
},
{ dataPointId: 'maxFrontEndLoad', comparatorCode: 'LT', value: 5 },
{ dataPointId: 'maxDeferredLoad', comparatorCode: 'LT', value: 5 }
]);
});
}

async function handleSelectEnvironment (evt) {
const target = evt.target;
const postmanJSON = await getPostmanJSON(target);

if (!postmanJSON) {
loadingLabel.textContent =
'The provided file is not a Postman Environment Configuration.';
loadingLabel.style.display = 'block';

return;
}

target.parentNode.style.display = 'none';

loadingLabel.style.display = 'block';
loadingLabel.textContent = 'Loading data…';

displayInvestmentScreener(postmanJSON);
}

document
.getElementById('postman-json')
.addEventListener('change', handleSelectEnvironment);

async function getPostmanJSON (htmlInputFile) {
let file;
let fileJSON;

for (file of htmlInputFile.files) {
try {
fileJSON = JSON.parse(await file.text());
if (Connectors.Morningstar.isPostmanEnvironmentJSON(fileJSON)) {
break;
}
} catch (error) {
// eslint-disable-next-line no-console
console.warn(error);
}
}

return fileJSON;
}
1 change: 1 addition & 0 deletions demos/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h1>Morningstar Connectors Demos</h1>
<li><a href="stock-ohlcv/demo.html">Highcharts Stock + Morningstar OHLCV TimeSeries</a></li>
<li><a href="stock-securitydetails/demo.html">Highcharts Stock + Morningstar Security Details</a></li>
<li><a href="dashboards-risk-score/demo.html">Highcharts Dashboards + Morningstar Risk Score</a></li>
<li><a href="dashboards-investment-screener/demo.html">Highcharts Dashboards + Morningstar Investment Screener</a></li>
<li><a href="manual-fetch/demo.html">Using fetch without connectors</a></li>
</ul>
</body>
Expand Down
1 change: 1 addition & 0 deletions docs/connectors/morningstar.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ types.
* [Risk Score](morningstar/risk-score.md)
* [TimeSeries](morningstar/time-series/time-series.md)
* [XRay](morningstar/x-ray.md)
* [Investment Screener](morningstar/screeners/investment-screener.md)
* [Security Details](morningstar/security-details.md)
Loading

0 comments on commit a9ef98d

Please sign in to comment.