wealth dashboard: spend-down table → three growth scenarios
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
Viktor wanted the spend-down card to compare three portfolio-growth scenarios rather than the previous floor-vs-4%-real pair. The table now has three rows, each a die-with-zero annuity (drain net worth to £0 by age 100) spending a constant number of ACTUAL (nominal) pounds, differing only by the assumed nominal growth rate: • No growth (0%) → £43/day, £1,315/mo, £15,776/yr (= NW ÷ years) • Inflation (3%) → £106/day, £3,233/mo, £38,792/yr (NEW) • Avg market (7%) → £220/day, £6,703/mo, £80,435/yr This keeps the £43 no-growth floor he anchored on. The old third row was "4% real" (£133) expressed in today's money; it's replaced by the 7%-nominal market row (£220, actual pounds) so all three rows share one basis (nominal pounds) and are directly comparable. 3%/7% are hardcoded (one-line SQL edit). Table height 4→5 for the extra row; panels below shifted down 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
85d42f2c13
commit
e89de86af0
1 changed files with 22 additions and 22 deletions
|
|
@ -838,7 +838,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 13
|
"y": 14
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -855,7 +855,7 @@
|
||||||
"h": 9,
|
"h": 9,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 14
|
"y": 15
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -927,7 +927,7 @@
|
||||||
"h": 9,
|
"h": 9,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 23
|
"y": 24
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -992,7 +992,7 @@
|
||||||
"h": 9,
|
"h": 9,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 23
|
"y": 24
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1092,7 +1092,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 33
|
"y": 34
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -1109,7 +1109,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 34
|
"y": 35
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1204,7 +1204,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 44
|
"y": 45
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1309,7 +1309,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 54
|
"y": 55
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1385,7 +1385,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 65
|
"y": 66
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -1402,7 +1402,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 66
|
"y": 67
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1515,7 +1515,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 77
|
"y": 78
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -1532,7 +1532,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 78
|
"y": 79
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1729,7 +1729,7 @@
|
||||||
"h": 10,
|
"h": 10,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 78
|
"y": 79
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1782,7 +1782,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 89
|
"y": 90
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -1799,7 +1799,7 @@
|
||||||
"h": 9,
|
"h": 9,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 90
|
"y": 91
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -1916,7 +1916,7 @@
|
||||||
"h": 12,
|
"h": 12,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 90
|
"y": 91
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -2135,7 +2135,7 @@
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 103
|
"y": 104
|
||||||
},
|
},
|
||||||
"panels": []
|
"panels": []
|
||||||
},
|
},
|
||||||
|
|
@ -2152,7 +2152,7 @@
|
||||||
"h": 12,
|
"h": 12,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 104
|
"y": 105
|
||||||
},
|
},
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
|
@ -2252,14 +2252,14 @@
|
||||||
{
|
{
|
||||||
"id": 9220,
|
"id": 9220,
|
||||||
"title": "Spend-down to \u00a30 at age 100",
|
"title": "Spend-down to \u00a30 at age 100",
|
||||||
"description": "How much you can spend to exhaust your net worth (pension included) by your 100th birthday (2098-10-04). FLOOR = treats the money as cash, no growth or inflation \u2014 a conservative lower bound. 4% REAL = die-with-zero annuity assuming the balance keeps earning 4% after inflation: PMT = NW\u00b7r/(1\u2212(1+r)^\u2212n). Computed live, so it drifts as net worth and the horizon move.",
|
"description": "How much you can spend to exhaust your net worth (pension included) by your 100th birthday (2098-10-04), draining to \u00a30. Three scenarios by how the pot grows: No growth (0%), Inflation (3% nominal), Market (7% nominal \u2014 the dashboard's base return assumption). Each is a die-with-zero annuity (PMT = NW\u00b7r/(1\u2212(1+r)^\u2212n); NW\u00f7years when r=0) spending a constant number of pounds. Figures are ACTUAL (nominal) pounds you'd withdraw \u2014 at higher growth, later pounds buy less. Rates hardcoded (one-line SQL edit to change). Computed live.",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"datasource": {
|
"datasource": {
|
||||||
"type": "grafana-postgresql-datasource",
|
"type": "grafana-postgresql-datasource",
|
||||||
"uid": "wealth-pg"
|
"uid": "wealth-pg"
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 4,
|
"h": 5,
|
||||||
"w": 9,
|
"w": 9,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 9
|
"y": 9
|
||||||
|
|
@ -2277,7 +2277,7 @@
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
"options": "Basis"
|
"options": "Scenario"
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
|
|
@ -2314,7 +2314,7 @@
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC), nw AS (SELECT SUM(total_value) AS pv FROM latest), calc AS (SELECT pv, (DATE '2098-10-04' - CURRENT_DATE)::float8 AS days, (DATE '2098-10-04' - CURRENT_DATE)::float8/365.25 AS years, 0.04::float8 AS r FROM nw), pmt AS (SELECT pv, days, years, r, pv*r/(1-power(1+r,-years)) AS annual FROM calc) SELECT b.label AS \"Basis\", round((CASE b.k WHEN 'floor' THEN pv/years/365.25 ELSE annual/365.25 END)::numeric,0) AS \"Per day\", round((CASE b.k WHEN 'floor' THEN pv/years/12 ELSE annual/12 END)::numeric,0) AS \"Per month\", round((CASE b.k WHEN 'floor' THEN pv/years ELSE annual END)::numeric,0) AS \"Per year\" FROM pmt, (VALUES (1,'floor','Floor'),(2,'real','4% real')) AS b(ord,k,label) ORDER BY b.ord"
|
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC), nw AS (SELECT SUM(total_value) AS pv FROM latest), hz AS (SELECT pv, (DATE '2098-10-04' - CURRENT_DATE)::float8/365.25 AS years FROM nw), scen AS (SELECT ord,label, CASE WHEN rate=0 THEN pv/years ELSE pv*rate/(1-power(1+rate,-years)) END AS annual FROM hz, (VALUES (1,'No growth',0.0::float8),(2,'Inflation (3%)',0.03::float8),(3,'Market (7%)',0.07::float8)) AS s(ord,label,rate)) SELECT label AS \"Scenario\", round((annual/365.25)::numeric,0) AS \"Per day\", round((annual/12)::numeric,0) AS \"Per month\", round(annual::numeric,0) AS \"Per year\" FROM scen ORDER BY ord"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue