infra/stacks/monitoring/modules/monitoring/dashboards/wealth.json
Viktor Barzin 126cfb7022 wealth: dav_corrected view fixes pension gains-offset miscategorisation
The broker-sync Fidelity provider emits 'unrealised-gains-offset'
DEPOSIT activities to reconcile Wealthfolio's total with the
PlanViewer reported pot, because Wealthfolio doesn't track pension
fund units directly. Wealthfolio's data model treats that DEPOSIT as
a cash contribution, which double-inflates net_contribution and
zeroes out the implied growth.

Add a Postgres view 'dav_corrected' in wealthfolio_sync that
subtracts the cumulative gains-offset from net_contribution per
account per date (re-exporting as 'net_contribution' so it's a
drop-in replacement). All 17 wealth dashboard panels that compute
contribution/growth/ROI now read from the view. Total impact:
portfolio Growth jumps from £301,753.19 to £337,474.39 (exactly
the £35,721.20 Fidelity offset that was previously miscategorised).
2026-05-22 14:16:52 +00:00

2169 lines
71 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"enable": true,
"hide": false,
"iconColor": "purple",
"name": "Milestones",
"target": {
"rawQuery": true,
"editorMode": "code",
"format": "table",
"refId": "Anno",
"rawSql": "WITH daily AS (SELECT d.valuation_date, SUM(d.total_value) AS nw FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date), crossings AS (SELECT t, (SELECT MIN(valuation_date) FROM daily WHERE nw >= t::numeric) AS d FROM unnest(ARRAY[100000, 250000, 500000, 750000, 1000000]) AS t) SELECT d::timestamptz AS time, '£' || CASE WHEN t >= 1000000 THEN (t/1000000)::int::text || 'M' ELSE (t/1000)::int::text || 'k' END AS text FROM crossings WHERE d IS NOT NULL ORDER BY d"
}
}
]
},
"description": "Wealth — net worth, contributions, and growth over time. Backed by the wealthfolio_sync PG mirror of Wealthfolio's SQLite, refreshed hourly by the pg-sync sidecar.",
"editable": true,
"fiscalYearStartMonth": 0,
"id": null,
"links": [],
"panels": [
{
"id": 1,
"title": "Net worth (current)",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 4,
"x": 0,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "fixed",
"fixedColor": "green"
},
"decimals": 2
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value, d.net_contribution, d.cash_balance, d.investment_market_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC) SELECT SUM(total_value) AS net_worth FROM latest"
}
]
},
{
"id": 2,
"title": "Net contribution (cumulative)",
"description": "Total deposits minus withdrawals across all accounts.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 4,
"x": 4,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "fixed",
"fixedColor": "blue"
},
"decimals": 2
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value, d.net_contribution, d.cash_balance, d.investment_market_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC) SELECT SUM(net_contribution) AS contribution FROM latest"
}
]
},
{
"id": 3,
"title": "Growth (unrealised)",
"description": "Net worth minus net contribution — the gain on everything you've put in.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 4,
"x": 8,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value, d.net_contribution, d.cash_balance, d.investment_market_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC) SELECT (SUM(total_value) - SUM(net_contribution)) AS growth FROM latest"
}
]
},
{
"id": 4,
"title": "ROI %",
"description": "Growth / net contribution × 100. Excludes accounts with zero/negative contribution (Schwab) to avoid distortion.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 3,
"x": 12,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "thresholds"
},
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 0
},
{
"color": "green",
"value": 5
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.total_value, d.net_contribution, d.cash_balance, d.investment_market_value FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC) SELECT (SUM(total_value - net_contribution) / NULLIF(SUM(net_contribution), 0) * 100) AS roi_pct FROM latest WHERE net_contribution > 0"
}
]
},
{
"id": 5,
"title": "Net worth — total over time",
"description": "Daily total_value summed across all accounts (base GBP). Declining segments overlaid in red.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 12
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed",
"fixedColor": "green"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 20,
"pointSize": 4,
"showPoints": "never",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "none"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "net_worth"
},
"properties": [
{
"id": "displayName",
"value": "Net worth"
}
]
},
{
"matcher": {
"id": "byName",
"options": "decline"
},
"properties": [
{
"id": "displayName",
"value": "Decline"
},
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "red"
}
},
{
"id": "custom.spanNulls",
"value": false
},
{
"id": "custom.fillOpacity",
"value": 0
},
{
"id": "custom.lineWidth",
"value": 3
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), series AS (SELECT valuation_date, SUM(total_value) AS nw FROM daily_account_valuation WHERE $__timeFilter(valuation_date) AND valuation_date <= (SELECT d FROM max_complete) GROUP BY valuation_date), labeled AS (SELECT valuation_date, nw, LAG(nw) OVER (ORDER BY valuation_date) AS prev_nw, LEAD(nw) OVER (ORDER BY valuation_date) AS next_nw FROM series) SELECT valuation_date::timestamp AS \"time\", nw AS net_worth, CASE WHEN (prev_nw IS NOT NULL AND nw < prev_nw) OR (next_nw IS NOT NULL AND next_nw < nw) THEN nw END AS decline FROM labeled ORDER BY valuation_date"
}
]
},
{
"id": 6,
"title": "Net contribution vs market value",
"description": "Net contribution = cumulative deposits withdrawals. Market value = total_value (cash + investments). Gap between the two = unrealised growth.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 22
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 0,
"pointSize": 4,
"showPoints": "never",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "none"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "market_value"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Market value"
}
]
},
{
"matcher": {
"id": "byName",
"options": "net_contribution"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "Net contribution"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)) SELECT valuation_date::timestamp AS \"time\", SUM(net_contribution) AS net_contribution, SUM(total_value) AS market_value FROM dav_corrected WHERE $__timeFilter(valuation_date) AND valuation_date <= (SELECT d FROM max_complete) GROUP BY valuation_date ORDER BY valuation_date"
}
]
},
{
"id": 7,
"title": "Growth (market value contribution) over time",
"description": "Unrealised gain across all accounts. Filled area to emphasise the wealth created above the contributed capital. Declining segments overlaid in red.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 12,
"y": 22
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed",
"fixedColor": "#56A64B"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 50,
"gradientMode": "opacity",
"pointSize": 4,
"showPoints": "never",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "none"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "growth"
},
"properties": [
{
"id": "displayName",
"value": "Growth"
}
]
},
{
"matcher": {
"id": "byName",
"options": "decline"
},
"properties": [
{
"id": "displayName",
"value": "Decline"
},
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "red"
}
},
{
"id": "custom.spanNulls",
"value": false
},
{
"id": "custom.fillOpacity",
"value": 0
},
{
"id": "custom.lineWidth",
"value": 3
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), series AS (SELECT valuation_date, (SUM(total_value) - SUM(net_contribution)) AS g FROM dav_corrected WHERE $__timeFilter(valuation_date) AND valuation_date <= (SELECT d FROM max_complete) GROUP BY valuation_date), labeled AS (SELECT valuation_date, g, LAG(g) OVER (ORDER BY valuation_date) AS prev_g, LEAD(g) OVER (ORDER BY valuation_date) AS next_g FROM series) SELECT valuation_date::timestamp AS \"time\", g AS growth, CASE WHEN (prev_g IS NOT NULL AND g < prev_g) OR (next_g IS NOT NULL AND next_g < g) THEN g END AS decline FROM labeled ORDER BY valuation_date"
}
]
},
{
"id": 8,
"title": "Per-account stacked — total value",
"description": "Stacked area showing each account's contribution to total net worth over time. Useful for spotting which account drives the trajectory.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 40
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"drawStyle": "line",
"lineWidth": 1,
"fillOpacity": 70,
"pointSize": 3,
"showPoints": "never",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "normal"
}
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)) SELECT d.valuation_date::timestamp AS \"time\", a.name AS metric, d.total_value AS value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE $__timeFilter(d.valuation_date) AND d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.valuation_date, a.name"
}
]
},
{
"id": 9,
"title": "Cash vs invested (stacked)",
"description": "Daily breakdown of uninvested broker cash vs market value of investments. WORKPLACE_PENSION accounts (Fidelity) are reclassified entirely as invested — Wealthfolio dumps pension wrappers into cash_balance because it doesn't track the underlying fund holdings, but they are not actually cash.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 51
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"drawStyle": "line",
"lineWidth": 1,
"fillOpacity": 70,
"pointSize": 3,
"showPoints": "never",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "normal"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "cash"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#FADE2A"
}
},
{
"id": "displayName",
"value": "Cash"
}
]
},
{
"matcher": {
"id": "byName",
"options": "invested"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#56A64B"
}
},
{
"id": "displayName",
"value": "Invested"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)) SELECT d.valuation_date::timestamp AS \"time\", SUM(CASE WHEN a.account_type = 'WORKPLACE_PENSION' THEN 0 ELSE d.cash_balance END) AS cash, SUM(CASE WHEN a.account_type = 'WORKPLACE_PENSION' THEN d.cash_balance + d.investment_market_value ELSE d.investment_market_value END) AS invested FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE $__timeFilter(d.valuation_date) AND d.valuation_date <= (SELECT d FROM max_complete) GROUP BY d.valuation_date ORDER BY d.valuation_date"
}
]
},
{
"id": 10,
"title": "Activity log",
"description": "Recent activities (BUY / SELL / DEPOSIT / WITHDRAWAL / DIVIDEND / etc.) across all accounts. Limited to 100 most recent.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 104
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "amount"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
}
]
},
"options": {
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "SELECT a.activity_date AS \"date\", acc.name AS \"account\", a.activity_type AS \"type\", a.asset_id AS \"asset\", a.quantity AS \"qty\", a.unit_price AS \"unit_price\", a.amount AS \"amount\", a.currency AS \"ccy\", a.notes AS \"notes\" FROM activities a LEFT JOIN accounts acc ON acc.id = a.account_id WHERE $__timeFilter(a.activity_date) ORDER BY a.activity_date DESC LIMIT 100"
}
]
},
{
"id": 11,
"title": "12mo 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": 3,
"x": 15,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "thresholds"
},
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 0
},
{
"color": "green",
"value": 5
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.valuation_date AS d_now, d.total_value AS nw_now, d.net_contribution AS contrib_now FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC), ago AS (SELECT DISTINCT ON (l.account_id) l.account_id, d.total_value AS nw_ago, d.net_contribution AS contrib_ago FROM latest l JOIN dav_corrected d ON d.account_id = l.account_id AND d.valuation_date <= l.d_now - INTERVAL '12 months' ORDER BY l.account_id, d.valuation_date DESC), agg AS (SELECT (SELECT SUM(nw_now) FROM latest) AS nw_now, (SELECT SUM(contrib_now) FROM latest) AS contrib_now, (SELECT SUM(nw_ago) FROM ago) AS nw_ago, (SELECT SUM(contrib_ago) FROM ago) AS contrib_ago) 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": 15,
"title": "12mo contrib",
"description": "Net contributions (deposits withdrawals) over the trailing 12 months. How much new money you put in — independent of market movement.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 3,
"x": 18,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "fixed",
"fixedColor": "blue"
},
"decimals": 2
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.valuation_date AS d_now, d.total_value AS nw_now, d.net_contribution AS contrib_now FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC), ago AS (SELECT DISTINCT ON (l.account_id) l.account_id, d.total_value AS nw_ago, d.net_contribution AS contrib_ago FROM latest l JOIN dav_corrected d ON d.account_id = l.account_id AND d.valuation_date <= l.d_now - INTERVAL '12 months' ORDER BY l.account_id, d.valuation_date DESC), agg AS (SELECT (SELECT SUM(nw_now) FROM latest) AS nw_now, (SELECT SUM(contrib_now) FROM latest) AS contrib_now, (SELECT SUM(nw_ago) FROM ago) AS nw_ago, (SELECT SUM(contrib_ago) FROM ago) AS contrib_ago) SELECT (contrib_now - contrib_ago) AS contrib_12mo FROM agg"
}
]
},
{
"id": 16,
"title": "12mo gain",
"description": "Trailing 12-month market gain in £ — the change in net worth minus net contributions. What the markets gave you, separate from money you added in.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 3,
"x": 21,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) d.account_id, d.valuation_date AS d_now, d.total_value AS nw_now, d.net_contribution AS contrib_now FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC), ago AS (SELECT DISTINCT ON (l.account_id) l.account_id, d.total_value AS nw_ago, d.net_contribution AS contrib_ago FROM latest l JOIN dav_corrected d ON d.account_id = l.account_id AND d.valuation_date <= l.d_now - INTERVAL '12 months' ORDER BY l.account_id, d.valuation_date DESC), agg AS (SELECT (SELECT SUM(nw_now) FROM latest) AS nw_now, (SELECT SUM(contrib_now) FROM latest) AS contrib_now, (SELECT SUM(nw_ago) FROM ago) AS nw_ago, (SELECT SUM(contrib_ago) FROM ago) AS contrib_ago) SELECT ((nw_now - nw_ago) - (contrib_now - contrib_ago)) AS gain_12mo FROM agg"
}
]
},
{
"id": 17,
"title": "Δ 1d (all)",
"description": "Net worth delta over the trailing window (latest snapshot minus snapshot from N days ago). Includes new money paid in (salary, vests, deposits) AND market gains.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '1 day' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT (SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap) AS delta"
}
]
},
{
"id": 21,
"title": "Δ 1d (mkt)",
"description": "Pure market gain over the trailing window — net-worth delta minus net-contribution delta. Excludes new money paid in. Apples-to-apples with the Growth panel.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 8
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '1 day' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT ((SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap)) - ((SELECT SUM(net_contribution) FROM now_snap) - (SELECT SUM(net_contribution) FROM past_snap)) AS market_delta"
}
]
},
{
"id": 18,
"title": "Δ 7d (all)",
"description": "Net worth delta over the trailing window (latest snapshot minus snapshot from N days ago). Includes new money paid in (salary, vests, deposits) AND market gains.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '7 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT (SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap) AS delta"
}
]
},
{
"id": 22,
"title": "Δ 7d (mkt)",
"description": "Pure market gain over the trailing window — net-worth delta minus net-contribution delta. Excludes new money paid in. Apples-to-apples with the Growth panel.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 8
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '7 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT ((SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap)) - ((SELECT SUM(net_contribution) FROM now_snap) - (SELECT SUM(net_contribution) FROM past_snap)) AS market_delta"
}
]
},
{
"id": 19,
"title": "Δ 30d (all)",
"description": "Net worth delta over the trailing window (latest snapshot minus snapshot from N days ago). Includes new money paid in (salary, vests, deposits) AND market gains.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '30 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT (SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap) AS delta"
}
]
},
{
"id": 23,
"title": "Δ 30d (mkt)",
"description": "Pure market gain over the trailing window — net-worth delta minus net-contribution delta. Excludes new money paid in. Apples-to-apples with the Growth panel.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 8
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '30 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT ((SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap)) - ((SELECT SUM(net_contribution) FROM now_snap) - (SELECT SUM(net_contribution) FROM past_snap)) AS market_delta"
}
]
},
{
"id": 20,
"title": "Δ 90d (all)",
"description": "Net worth delta over the trailing window (latest snapshot minus snapshot from N days ago). Includes new money paid in (salary, vests, deposits) AND market gains.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value FROM daily_account_valuation d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '90 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT (SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap) AS delta"
}
]
},
{
"id": 24,
"title": "Δ 90d (mkt)",
"description": "Pure market gain over the trailing window — net-worth delta minus net-contribution delta. Excludes new money paid in. Apples-to-apples with the Growth panel.",
"type": "stat",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 8
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"color": {
"mode": "thresholds"
},
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), now_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC), past_snap AS (SELECT DISTINCT ON (d.account_id) d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id WHERE d.valuation_date <= (SELECT d - INTERVAL '90 days' FROM max_complete) ORDER BY d.account_id, d.valuation_date DESC) SELECT ((SELECT SUM(total_value) FROM now_snap) - (SELECT SUM(total_value) FROM past_snap)) - ((SELECT SUM(net_contribution) FROM now_snap) - (SELECT SUM(net_contribution) FROM past_snap)) AS market_delta"
}
]
},
{
"id": 12,
"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": 61
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"unit": "percent",
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 0
},
{
"color": "green",
"value": 5
}
]
},
"custom": {
"axisPlacement": "auto",
"axisLabel": "",
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "year"
},
"properties": [
{
"id": "unit",
"value": "string"
}
]
}
]
},
"options": {
"barRadius": 0,
"barWidth": 0.6,
"groupWidth": 0.7,
"orientation": "auto",
"showValue": "always",
"stacking": "none",
"xField": "year",
"xTickLabelRotation": 0,
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), yearly AS (SELECT EXTRACT(YEAR FROM valuation_date)::int AS yr, valuation_date, SUM(total_value) AS nw, SUM(net_contribution) AS contrib FROM dav_corrected WHERE valuation_date <= (SELECT d FROM max_complete) 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"
}
]
},
{
"id": 13,
"title": "Annual change decomposition — contributions vs market gain",
"description": "Each calendar year's net worth change split into 'new money in' (contributions withdrawals) and 'market gain' (everything else: price appreciation, dividends, etc.). Shows whether you grew because you saved or because the market did the work. Negative bars = withdrawals or market losses.",
"type": "barchart",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 72
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 2,
"custom": {
"axisPlacement": "auto",
"axisLabel": "",
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "year"
},
"properties": [
{
"id": "unit",
"value": "string"
}
]
},
{
"matcher": {
"id": "byName",
"options": "contributions"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "Net contributions"
}
]
},
{
"matcher": {
"id": "byName",
"options": "market_gain"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#56A64B"
}
},
{
"id": "displayName",
"value": "Market gain"
}
]
}
]
},
"options": {
"barRadius": 0,
"barWidth": 0.6,
"groupWidth": 0.7,
"orientation": "auto",
"showValue": "auto",
"stacking": "normal",
"xField": "year",
"xTickLabelRotation": 0,
"legend": {
"calcs": [
"sum"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), yearly AS (SELECT EXTRACT(YEAR FROM valuation_date)::int AS yr, valuation_date, SUM(total_value) AS nw, SUM(net_contribution) AS contrib FROM dav_corrected WHERE valuation_date <= (SELECT d FROM max_complete) 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((contrib_end - contrib_start)::numeric, 0) AS contributions, ROUND((nw_end - nw_start - (contrib_end - contrib_start))::numeric, 0) AS market_gain FROM endpoints ORDER BY yr"
}
]
},
{
"id": 25,
"title": "Monthly contributions vs market gain",
"description": "Each month's net contributions vs market gain as two lines. Where the green (market) line crosses above the blue (contributions) line is when investments out-earn savings for that month. Months below zero on the green line = market drawdowns.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 83
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 0,
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 0,
"pointSize": 5,
"showPoints": "auto",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {
"group": "A",
"mode": "none"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "contributions"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "Net contributions"
}
]
},
{
"matcher": {
"id": "byName",
"options": "market_gain"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#56A64B"
}
},
{
"id": "displayName",
"value": "Market gain"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"sum"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH active_count AS (SELECT COUNT(*) AS n FROM accounts), max_complete AS (SELECT MAX(valuation_date) AS d FROM (SELECT d.valuation_date, COUNT(*) AS c FROM dav_corrected d JOIN accounts a ON a.id = d.account_id GROUP BY d.valuation_date) x WHERE c >= (SELECT n FROM active_count)), monthly AS (SELECT date_trunc('month', valuation_date)::date AS month, valuation_date, SUM(total_value) AS nw, SUM(net_contribution) AS contrib FROM dav_corrected WHERE valuation_date <= (SELECT d FROM max_complete) GROUP BY valuation_date), endpoints AS (SELECT month, (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 monthly GROUP BY month) SELECT month::timestamp AS time, ROUND((contrib_end - contrib_start)::numeric, 0) AS contributions, ROUND((nw_end - nw_start - (contrib_end - contrib_start))::numeric, 0) AS market_gain FROM endpoints ORDER BY month"
}
]
},
{
"id": 14,
"title": "Per-account ROI %",
"description": "(market value net contribution) / net contribution × 100, latest snapshot. Excludes accounts with zero/negative net contribution (Schwab — RSU vests sold = negative contribution distorts the ratio). Pension shows 0% because Wealthfolio doesn't track underlying fund holdings, so cost_basis = 0 and 'growth' is just the cash balance reported.",
"type": "barchart",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 94
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"unit": "percent",
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 0
},
{
"color": "green",
"value": 10
}
]
},
"custom": {
"axisPlacement": "auto",
"axisLabel": "",
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1
}
},
"overrides": []
},
"options": {
"barRadius": 0,
"barWidth": 0.6,
"groupWidth": 0.7,
"orientation": "horizontal",
"showValue": "always",
"stacking": "none",
"xField": "account",
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (d.account_id) a.name, d.total_value, d.net_contribution FROM dav_corrected d JOIN accounts a ON a.id = d.account_id ORDER BY d.account_id, d.valuation_date DESC) SELECT name AS account, ROUND(((total_value - net_contribution) / NULLIF(net_contribution, 0) * 100)::numeric, 2) AS roi_pct FROM latest WHERE net_contribution > 0 ORDER BY roi_pct DESC"
}
]
},
{
"id": 26,
"title": "Positions",
"description": "Currently-held positions: shares, cost basis, latest market price, and unrealised return. Latest holdings_snapshots TOTAL aggregate + latest quote per asset.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 32
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "shares"
},
"properties": [
{
"id": "decimals",
"value": 2
}
]
},
{
"matcher": {
"id": "byName",
"options": "avg cost"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "last"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "market value"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "cost"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "gain"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "currencyGBP"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-text"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "return %"
},
"properties": [
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "percent"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-text"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
}
]
}
]
},
"options": {
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "SELECT a.symbol, a.name, p.quantity AS shares, p.average_cost AS \"avg cost\", q.close AS \"last\", (p.quantity * q.close) AS \"market value\", p.total_cost_basis AS cost, ((p.quantity * q.close) - p.total_cost_basis) AS gain, CASE WHEN p.total_cost_basis > 0 THEN ((p.quantity * q.close) / p.total_cost_basis - 1) * 100 END AS \"return %\", p.currency, q.day AS \"as of\" FROM positions_latest p LEFT JOIN assets a ON a.id = p.asset_id LEFT JOIN quote_latest q ON q.asset_id = p.asset_id ORDER BY (p.quantity * q.close) DESC NULLS LAST"
}
]
}
],
"refresh": "5m",
"schemaVersion": 39,
"tags": [
"finance",
"personal",
"wealth"
],
"templating": {
"list": []
},
"time": {
"from": "now-180d",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Wealth",
"uid": "wealth",
"version": 1
}