Skip to content

Commit

Permalink
Added metadata to RiskScoreConnector and improved demo (#39)
Browse files Browse the repository at this point in the history
* Put messages from API in connector metadata
* Improve demo
* Filter columns in data grid
* Update docs for risk-score
  • Loading branch information
Eskils authored Sep 30, 2024
1 parent 479c379 commit a754620
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 79 deletions.
4 changes: 0 additions & 4 deletions demos/dashboards-risk-score/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ body {
border-bottom: none;
}

#dashboard-col-1 .highcharts-dashboards-component-title {
background-color: #336;
}

@media screen and (max-width: 1000px) {
.row {
flex-direction: column;
Expand Down
209 changes: 168 additions & 41 deletions demos/dashboards-risk-score/demo.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,107 @@
const loadingLabel = document.getElementById('loading-label');

function displayRiskScore (postmanJSON) {
Dashboards.board('container', {

const highRiskRetirementPortfolio = {
name: 'HighRisk',
currency: 'USD',
totalValue: 100,
holdings: [
{
id: 'VTIAX',
idType: 'TradingSymbol',
weight: 33
},
{
id: 'POGRX',
idType: 'TradingSymbol',
weight: 20
},
{
id: 'OAKMX',
idType: 'TradingSymbol',
weight: 20
},
{
id: 'VEXAX',
idType: 'TradingSymbol',
weight: 15
},
{
id: 'OAKEX',
idType: 'TradingSymbol',
weight: 7
},
{
id: 'MWTRX',
idType: 'TradingSymbol',
weight: 5
}
]
};

const lowRiskRetirementPortfolio = {
name: 'LowRisk',
currency: 'USD',
totalValue: 100,
holdings: [
{
id: 'VTIAX',
idType: 'TradingSymbol',
weight: 10
},
{
id: 'POGRX',
idType: 'TradingSymbol',
weight: 10
},
{
id: 'VDADX',
idType: 'TradingSymbol',
weight: 10
},
{
id: 'OAKMX',
idType: 'TradingSymbol',
weight: 10
},
{
id: 'VEXAX',
idType: 'TradingSymbol',
weight: 5
},
{
id: 'OAKEX',
idType: 'TradingSymbol',
weight: 5
},
{
id: 'MWTRX',
idType: 'TradingSymbol',
weight: 30
},
{
id: 'FSHBX',
idType: 'TradingSymbol',
weight: 10
},
{
id: 'VTAPX',
idType: 'TradingSymbol',
weight: 10
}
]
}

const board = Dashboards.board('container', {
dataPool: {
connectors: [{
id: 'risk-score',
type: 'MorningstarRiskScore',
options: {
portfolios: [
{
name: 'TestPortfolio1',
currency: 'USD',
totalValue: 100,
holdings: [
{
id: 'F00000VCTT',
idType: 'SecurityID',
weight: 50
},
{
id: 'AAPL',
idType: 'TradingSymbol',
weight: 50
}
]
}
lowRiskRetirementPortfolio,
highRiskRetirementPortfolio
],
postman: {
environmentJSON: postmanJSON
Expand All @@ -35,44 +112,94 @@ function displayRiskScore (postmanJSON) {
components: [
{
renderTo: 'dashboard-col-0',
connector: {
id: 'risk-score',
columnAssignment: [
{
seriesId: 'low-risk',
data: ['LowRisk_RiskScore']
},
{
seriesId: 'high-risk',
data: ['HighRisk_RiskScore']
}
]
},
type: 'Highcharts',
chartOptions: {
chart: {
animation: false,
type: 'column'
},
credits: {
enabled: false
},
title: {
text: 'Risk score for each portfolio'
},
subtitle: {
text: 'Conservative is a low risk portfolio, ' +
'while Aggressive is a high risk portfolio'
},
yAxis: {
title: {
text: 'Risk Score'
}
},
xAxis: {
categories: ['Conservative', 'Aggressive']
},
series: [
{
id: 'low-risk',
name: 'Conservative',
tooltip: {
headerFormat: 'Stock/Bond ratio: 50/50<br>',
useHTML: true
}
},
{
id: 'high-risk',
name: 'Aggressive',
tooltip: {
headerFormat: 'Stock/Bond ratio: 95/5<br>',
useHTML: true
}
}
]
}
},
{
renderTo: 'dashboard-col-1',
connector: {
id: 'risk-score'
},
visibleColumns: [
'LowRisk_RiskScore',
'HighRisk_RiskScore'
],
type: 'DataGrid',
title: 'RiskScore',
dataGridOptions: {
editable: false,
columns: {
'TestPortfolio1_EffectiveDate': {
headerFormat: 'Date',
cellFormatter: function () {
return new Date(this.value)
.toISOString()
.substring(0, 10);
}
'LowRisk_RiskScore': {
headerFormat: 'RiskScore, Conservative'
},
'TestPortfolio1_RiskScore': {
headerFormat: 'RiskScore'
},
'TestPortfolio1_AlignmentScore': {
headerFormat: 'AlignmentScore'
},
'TestPortfolio1_RSquared': {
headerFormat: 'RSquared'
},
'TestPortfolio1_RetainedWeightProxied': {
headerFormat: 'RetainedWeight'
},
'TestPortfolio1_ScoringMethodUsed': {
headerFormat: 'ScoringMethod'
'HighRisk_RiskScore': {
headerFormat: 'RiskScore, Aggressive'
}
}
}
}
]
});

loadingLabel.style.display = 'none';

board.dataPool
.getConnectorTable('risk-score')
.then(() => {
loadingLabel.style.display = 'none';
});
}

async function handleSelectEnvironment (evt) {
Expand Down
15 changes: 11 additions & 4 deletions docs/connectors/morningstar/risk-score/risk-score.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,26 @@ Use the `RiskScoreConnector` to load risk scores.

In dashboards, this connector is called `MorningstarRiskScore`.

Specify the holdings in a portfolio in the options along with a postman environment
Specify the portfolio holdings in the options along with a postman environment
file for authentication, and other parameters such as `currency`.

### Holdings

Holdings are the securities that make up the portfolio. You can specify a holding using different kinds of id’s.
Holdings are the securities that make up the portfolio. You can specify a
holding using different kinds of id’s.

Supported id-types are: `CUSIP`, `FundCode`, `ISIN`, `MSID`, `PerformanceId`,
`SecurityID`, `TradingSymbol`.

You can specify the quantity of this holding in the portfolio by using either `weight` or `value`. If you decide to use `weight`, you need to specify the `totalValue` of the portfolio.
You can specify the quantity of this holding in the portfolio by using either
`weight` or `value`. If you decide to use `weight`, you need to specify
the `totalValue` of the portfolio.

> **NOTE:** You cannot mix and match `weight` and `value`. Be consistent and stick to one.
> **NOTE:** You cannot mix and match `weight` and `value`.
Be consistent and stick to one.

If you specify any holdings that are invalid, the connector will still yield
a result. The invalid holdings are in the connector’s `metadata` after load.

For more details, see [Morningstar’s RiskScore API].

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/RiskScore/RiskScoreConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import External from '../Shared/External';
import MorningstarConnector from '../Shared/MorningstarConnector';
import MorningstarAPI from '../Shared/MorningstarAPI';
import MorningstarURL from '../Shared/MorningstarURL';
import RiskScoreOptions, { BaseRiskScorePortfolio, RiskScorePortfolio } from './RiskScoreOptions';
import RiskScoreOptions, { BaseRiskScorePortfolio, RiskScoreMetadata, RiskScorePortfolio } from './RiskScoreOptions';
import RiskScoreConverter from './RiskScoreConverter';
import { HoldingIdentiferType, MorningstarHoldingOptions, MorningstarHoldingValueOptions, MorningstarHoldingWeightOptions } from '../Shared/MorningstarOptions';

Expand Down Expand Up @@ -197,6 +197,7 @@ export class RiskScoreConnector extends MorningstarConnector {
super(options);

this.converter = new RiskScoreConverter(options.converter);
this.metadata = this.converter.metadata;
this.options = options;
}

Expand All @@ -210,6 +211,10 @@ export class RiskScoreConnector extends MorningstarConnector {

public override readonly converter: RiskScoreConverter;

/**
* Metadata from the previous load.
*/
public override readonly metadata: RiskScoreMetadata;

public override readonly options: RiskScoreOptions;

Expand Down
27 changes: 13 additions & 14 deletions src/RiskScore/RiskScoreConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import MorningstarConverter from '../Shared/MorningstarConverter';
import RiskScoreJSON from './RiskScoreJSON';
import { RiskScoreConverterOptions } from './RiskScoreOptions';
import { RiskScoreConverterOptions, RiskScoreMetadata, RiskScoreMetadataMessage } from './RiskScoreOptions';


/* *
Expand Down Expand Up @@ -78,6 +78,10 @@ export class RiskScoreConverter extends MorningstarConverter {
super(options);

this.options = options as Required<RiskScoreConverterOptions>;
this.metadata = {
columns: {},
messages: []
};
}

/* *
Expand All @@ -92,6 +96,11 @@ export class RiskScoreConverter extends MorningstarConverter {
*/
public override readonly options: Required<RiskScoreConverterOptions>;

/**
* Metadata from the previous load.
*/
public readonly metadata: RiskScoreMetadata;

/* *
*
* Functions
Expand Down Expand Up @@ -124,7 +133,7 @@ export class RiskScoreConverter extends MorningstarConverter {
// Parse and cumulate risk scores by date

const sortedPortfolios: RiskScorePortfolio[] = [];
const errors: string[] = [];
const messages: RiskScoreMetadataMessage[] = [];

for (const riskScorePortfolio of json.riskScores) {
const {
Expand Down Expand Up @@ -153,15 +162,7 @@ export class RiskScoreConverter extends MorningstarConverter {
riskScorePortfolio.metadata !== undefined &&
riskScorePortfolio.metadata.messages.length > 0
) {
for (const message of riskScorePortfolio.metadata.messages) {
const holdingNames = message.invalidHoldings.map(
invalidHolding => invalidHolding.identifier
);

errors.push(
`The holding(s) ${holdingNames.join(', ')} are invalid. ${message.message}`
);
}
messages.push(...riskScorePortfolio.metadata.messages);
}
}

Expand Down Expand Up @@ -212,9 +213,7 @@ export class RiskScoreConverter extends MorningstarConverter {
}
}

if (errors.length > 0) {
throw new Error(errors.join('\n'));
}
this.metadata.messages = messages;

return true;
}
Expand Down
Loading

0 comments on commit a754620

Please sign in to comment.