Portfolio Proposal

Compares current and proposed portfolio allocations with performance and risk metrics for decision-making.

HTML

<script src="https://code.highcharts.com/maps/highmaps.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
<script src="https://code.highcharts.com/modules/dumbbell.js"></script>
<script src="https://code.highcharts.com/modules/series-label.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/grid-pro.js"></script>
<script src="https://code.highcharts.com/connectors/morningstar/connectors-morningstar.js"></script>

<header>
    <h1>Portfolio Proposal</h1>
    <p>Current vs. proposed allocation, risk and historical performance</p>
</header>
<div class="chart-row">
    <div class="chart-container" id="stress-chart"></div>
    <div class="chart-container" id="growth-chart"></div>
</div>
<div class="chart-row">
    <div class="chart-container risk-return">
        <div id="risk-return-wrapper">
            <div id="risk-return-chart"></div>
        </div>
        <div class="data-grids">
            <div class="data-grid" id="assets-grid"></div>
            <div class="data-grid" id="models-grid"></div>
        </div>
    </div>
    <div class="chart-container holdings-container">
        <div id="current-holdings-chart"></div>
        <div id="proposed-holdings-chart"></div>
    </div>
</div>
<div class="chart-row">
    <div class="chart-container" id="portfolio-chart"></div>
    <div class="chart-container" id="portfolio-grid"></div>
</div>
<div class="chart-row">
    <div class="chart-container" id="map-chart"></div>
</div>
<div class="loading-overlay" id="loading-overlay">
    <div class="loading-spinner"></div>
</div>

CSS

@import url("https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/css/grid-pro.css");

:root {
    color-scheme: light dark;

    --page-background: light-dark(#f4f3ee, #1b1b1b);
    --card-background: light-dark(#fff, #141414);
    --card-border: light-dark(#dcdbd2, #2c2c2c);
    --text-color: light-dark(#1e1e1e, #d0d0d0);
    --muted-text-color: light-dark(#5a5d5c, #a1a1a1);
    --card-shadow: light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.4));
}

body {
    display: flex;
    flex-direction: column;
    margin: 0;
    font-family: Arial, sans-serif;
    gap: 16px;
    background-color: var(--page-background);
    color: var(--text-color);
    padding: 24px;
}

header {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

h1 {
    margin: 0;
    font-size: 23px;
    font-weight: 600;
    color: var(--text-color);
}

header p {
    margin: 0;
    font-size: 15px;
    color: var(--muted-text-color);
}

.chart-row {
    display: flex;
    flex-direction: row;
    gap: 16px;
}

.chart-row div {
    flex: 1 0 0;
}

#portfolio-grid .hcg-container div {
    flex: none;
}

#stress-chart,
#growth-chart,
#risk-return-chart,
#current-holdings-chart,
#proposed-holdings-chart,
#portfolio-chart,
#map-chart {
    min-height: 400px;
}

#portfolio-grid {
    min-height: 400px !important;
}

.chart-container {
    flex: 1;
    background: var(--card-background);
    border: 1px solid var(--card-border);
    border-radius: 10px;
    box-shadow: 0 2px 1px var(--card-shadow);
    padding: 20px;
}

.chart-container.risk-return {
    display: flex;
    flex-direction: row;
}

.holdings-container {
    display: flex;
    min-width: 0;
    flex: 1 0 0;
}

.holdings-container #current-holdings-chart {
    min-width: 0;
    flex: 1;
}

.theme-custom {
    --hcg-font-size: 15px;
    --hcg-color: var(--text-color);
    --hcg-header-color: light-dark(#474554, #a1a1a1);
    --hcg-header-padding: 14px;
    --hcg-background: var(--card-background);
    --hcg-padding: 10px;
   ...

JavaScript

Highcharts.SVGRenderer.prototype.symbols.triangleLeft = (x, y, w, h) => [
    ['M', x, y + h / 2],
    ['L', x + w, y],
    ['L', x + w, y + h],
    ['Z']
];

Highcharts.SVGRenderer.prototype.symbols.triangleRight = (x, y, w, h) => [
    ['M', x + w, y + h / 2],
    ['L', x, y],
    ['L', x, y + h],
    ['Z']
];

// Health is calculated as DividendGrowth 1y vs 3y
const healthMap = {
    up: '▲', // "📈",
    down: '▼', // "📉",
    unchanged: ''
};

// Define constants and utility functions
const predefinedHoldings = [
    // Cash assets
    {
        id: 'US0250818038',
        idType: 'ISIN',
        type: 'FO',
        weight: 15,
        name: 'American Century Capital Preservation Fund Investor Class',
        symbol: 'CPFXX',
        proposedWeight: 12,
        // Performance is based on Security Performance M0-M255, without current
        // price
        performance: [
            3.489756,
            1.578978,
            2.406443,
            4.033827,
            4.424365,
            2.000312,
            0.962403,
            0.341273,
            1.482696
        ],
        health: 0, // No dividend
        rating: 5
    },
    // Bonds
    {
        id: 'US61747C7074', // Morgan Stanley Instl Liquidity Govt Portfolio
        idType: 'ISIN',
        type: 'FO',
        weight: 10,
        name: 'Morgan Stanley Instl Liquidity Govt Portfolio',
        symbol: 'MSILX',
        proposedWeight: 8,
        performance: [
            6.441144,
            2.326492,
            11.612653,
            8.407705,
            7.397144,
            14.58974,
            4.214963,
            10.441094,
            14.269209
        ],
        health: 0, // No dividend
        rating: 4
    },
    {
        id: 'US46637K2814', // JPMorgan Hedged Equity Fund Class I
        idType: 'ISIN',
        type: 'FO',
        weight: 8,
        name: 'JPMorgan Hedged Equity Fund Class I',
        symbol: 'JHEQX',
        proposedWeight: 10,
        performance: [
        ...