monitoring: investment-only returns + YoY YTD gross line chart
Wealth dashboard: - "Yearly growth %" → "Yearly investment return %": switched to modified-Dietz formula `market_gain / (nw_start + 0.5 × contributions)` so contributions don't inflate the return. New money in is excluded — this is portfolio performance, not net-worth change. - "Trailing 12-month growth %" → "Trailing 12-month investment return %": same formula, applied to the trailing 12mo window. Pre-fix vs post-fix: 2020: 155.0% → 5.12% (large contributions on small base) 2021: 344.7% → 26.45% 2022: 26.9% → -25.65% (the actual 2022 bear market) 2023: 123.2% → 41.60% 2024: 87.4% → 25.70% 2025: 46.8% → 8.43% 2026: 16.7% → 3.28% (YTD) UK Payslip dashboard: - Replaced the per-tax-year stacked bar with a year-over-year line chart: one line per tax year, X = month-of-tax-year (April→March, projected onto a 1970/71 fiscal calendar so years overlay), Y = cumulative YTD gross. Five+ lines visible at a glance for trend comparison.
This commit is contained in:
parent
55d1da41f6
commit
77bed10a51
2 changed files with 20 additions and 58 deletions
|
|
@ -2538,9 +2538,9 @@
|
|||
},
|
||||
{
|
||||
"id": 17,
|
||||
"title": "Gross composition by tax year — salary / bonus / RSU / other",
|
||||
"description": "Per-tax-year stacked bar of gross pay broken into earned components: salary, bonus, RSU vest value, and other (overtime, benefits-in-kind, etc.). Bar height = total gross. Compare year-over-year trends in base salary growth, bonus levels, and RSU vest sizing. Always shows all years — ignores the time picker.",
|
||||
"type": "barchart",
|
||||
"title": "YTD gross salary — year-over-year comparison",
|
||||
"description": "Cumulative gross pay built up month by month within each UK tax year (April → March). One line per tax year. Pay dates are projected onto a 1970/71 fiscal calendar so years overlay cleanly — the X-axis shows month-of-tax-year (April first, March last). Always shows all years; ignores the time picker.",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "payslips-pg"
|
||||
|
|
@ -2557,58 +2557,20 @@
|
|||
"unit": "currencyGBP",
|
||||
"decimals": 0,
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 0,
|
||||
"pointSize": 5,
|
||||
"showPoints": "auto",
|
||||
"spanNulls": true,
|
||||
"axisPlacement": "auto",
|
||||
"axisLabel": "",
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"lineWidth": 1
|
||||
"stacking": {"group": "A", "mode": "none"}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "salary"},
|
||||
"properties": [
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "green"}},
|
||||
{"id": "displayName", "value": "Salary"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "bonus"},
|
||||
"properties": [
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "#FADE2A"}},
|
||||
{"id": "displayName", "value": "Bonus"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "rsu"},
|
||||
"properties": [
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "#3274D9"}},
|
||||
{"id": "displayName", "value": "RSU vest"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "other"},
|
||||
"properties": [
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "#888888"}},
|
||||
{"id": "displayName", "value": "Other"}
|
||||
]
|
||||
}
|
||||
]
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"barRadius": 0,
|
||||
"barWidth": 0.6,
|
||||
"groupWidth": 0.7,
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
"stacking": "normal",
|
||||
"xField": "tax_year",
|
||||
"xTickLabelRotation": 0,
|
||||
"legend": {
|
||||
"calcs": ["sum"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"legend": {"calcs": ["last", "max"], "displayMode": "table", "placement": "bottom"},
|
||||
"tooltip": {"mode": "multi", "sort": "desc"}
|
||||
},
|
||||
"targets": [
|
||||
|
|
@ -2620,8 +2582,8 @@
|
|||
},
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"format": "table",
|
||||
"rawSql": "SELECT tax_year, SUM(salary) AS salary, SUM(bonus) AS bonus, SUM(rsu_vest) AS rsu, SUM(GREATEST(gross_pay - salary - bonus - rsu_vest, 0)) AS other FROM payslip_ingest.payslip GROUP BY tax_year ORDER BY tax_year"
|
||||
"format": "time_series",
|
||||
"rawSql": "SELECT (DATE '1970-04-06' + (pay_date - MAKE_DATE(SUBSTRING(tax_year, 1, 4)::int, 4, 6)))::timestamp AS \"time\", tax_year AS metric, SUM(gross_pay) OVER (PARTITION BY tax_year ORDER BY pay_date) AS ytd_gross FROM payslip_ingest.payslip ORDER BY tax_year, pay_date"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,8 +439,8 @@
|
|||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "Trailing 12-month growth %",
|
||||
"description": "% change in net worth over the trailing 12 months. Captures market momentum + new contributions combined.",
|
||||
"title": "Trailing 12-month investment return %",
|
||||
"description": "Modified-Dietz return over the trailing 12 months: market_gain / (nw_12mo_ago + 0.5 × contributions_12mo). Excludes new money in — answers 'how did my investments perform' rather than 'how much did my net worth change'.",
|
||||
"type": "stat",
|
||||
"datasource": {"type": "grafana-postgresql-datasource", "uid": "wealth-pg"},
|
||||
"gridPos": {"h": 4, "w": 24, "x": 0, "y": 59},
|
||||
|
|
@ -475,14 +475,14 @@
|
|||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"format": "table",
|
||||
"rawSql": "WITH t12 AS (SELECT (SELECT SUM(total_value) FROM daily_account_valuation WHERE valuation_date = (SELECT MAX(valuation_date) FROM daily_account_valuation)) AS now_nw, (SELECT SUM(total_value) FROM daily_account_valuation WHERE valuation_date = (SELECT MIN(valuation_date) FROM daily_account_valuation WHERE valuation_date >= (SELECT MAX(valuation_date) - INTERVAL '12 months' FROM daily_account_valuation))) AS yr_ago_nw) SELECT ROUND(((now_nw - yr_ago_nw) / NULLIF(yr_ago_nw, 0) * 100)::numeric, 2) AS pct_12mo FROM t12"
|
||||
"rawSql": "WITH bounds AS (SELECT (SELECT MAX(valuation_date) FROM daily_account_valuation) AS d_now, (SELECT MIN(valuation_date) FROM daily_account_valuation WHERE valuation_date >= (SELECT MAX(valuation_date) - INTERVAL '12 months' FROM daily_account_valuation)) AS d_ago), agg AS (SELECT (SELECT SUM(total_value) FROM daily_account_valuation WHERE valuation_date = b.d_now) AS nw_now, (SELECT SUM(net_contribution) FROM daily_account_valuation WHERE valuation_date = b.d_now) AS contrib_now, (SELECT SUM(total_value) FROM daily_account_valuation WHERE valuation_date = b.d_ago) AS nw_ago, (SELECT SUM(net_contribution) FROM daily_account_valuation WHERE valuation_date = b.d_ago) AS contrib_ago FROM bounds b) SELECT ROUND((((nw_now - nw_ago - (contrib_now - contrib_ago)) / NULLIF(nw_ago + 0.5 * (contrib_now - contrib_ago), 0)) * 100)::numeric, 2) AS pct_12mo FROM agg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"title": "Yearly growth %",
|
||||
"description": "% change in net worth from the first to last valuation in each calendar year. Includes both market gains and new contributions — see Panel 13 for the decomposition.",
|
||||
"title": "Yearly investment return %",
|
||||
"description": "Modified-Dietz return per calendar year: market_gain / (nw_start + 0.5 × contributions). Pure investment performance — excludes new contributions, so a £100k vest doesn't show as 100% growth. Negative bars = market losses (e.g., 2022 bear market).",
|
||||
"type": "barchart",
|
||||
"datasource": {"type": "grafana-postgresql-datasource", "uid": "wealth-pg"},
|
||||
"gridPos": {"h": 11, "w": 24, "x": 0, "y": 63},
|
||||
|
|
@ -528,7 +528,7 @@
|
|||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"format": "table",
|
||||
"rawSql": "WITH yearly AS (SELECT EXTRACT(YEAR FROM valuation_date)::int AS yr, valuation_date, SUM(total_value) AS nw FROM daily_account_valuation GROUP BY valuation_date), endpoints AS (SELECT yr, (array_agg(nw ORDER BY valuation_date ASC))[1] AS nw_start, (array_agg(nw ORDER BY valuation_date DESC))[1] AS nw_end FROM yearly GROUP BY yr) SELECT yr::text AS year, ROUND(((nw_end - nw_start) / NULLIF(nw_start, 0) * 100)::numeric, 2) AS growth_pct FROM endpoints WHERE nw_start > 0 ORDER BY yr"
|
||||
"rawSql": "WITH yearly AS (SELECT EXTRACT(YEAR FROM valuation_date)::int AS yr, valuation_date, SUM(total_value) AS nw, SUM(net_contribution) AS contrib FROM daily_account_valuation GROUP BY valuation_date), endpoints AS (SELECT yr, (array_agg(nw ORDER BY valuation_date ASC))[1] AS nw_start, (array_agg(nw ORDER BY valuation_date DESC))[1] AS nw_end, (array_agg(contrib ORDER BY valuation_date ASC))[1] AS contrib_start, (array_agg(contrib ORDER BY valuation_date DESC))[1] AS contrib_end FROM yearly GROUP BY yr) SELECT yr::text AS year, ROUND((((nw_end - nw_start - (contrib_end - contrib_start)) / NULLIF(nw_start + 0.5 * (contrib_end - contrib_start), 0)) * 100)::numeric, 2) AS return_pct FROM endpoints WHERE (nw_start + 0.5 * (contrib_end - contrib_start)) > 0 ORDER BY yr"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue