monitoring(wealth): monthly contributions vs market gain bar chart

Goal stated by user: see when monthly market gain starts to exceed
monthly contributions, i.e. the inflection point where the market is
out-earning savings rather than the other way around.

New panel id=25 between the annual decomposition (13) and per-account
ROI (14): bar chart with two side-by-side bars per month --
contributions (blue) and market gain (green). Same calculation as
panel 13 but month-grain instead of year-grain. Months where the
green bar dwarfs the blue one are visible at a glance.

SQL: same endpoints CTE pattern as panel 13, with date_trunc('month',
valuation_date) as the grouping key. Uses max_complete cutoff so
partial-today doesn't skew the latest month.

Layout: panels at y >= 75 shifted down by 11 (chart height). New
chart at y=75; panel 14 (per-account ROI) -> y=86; panel 10
(activity log) -> y=96.

Spot check (recent months from PG):
  2025-07: contrib +£5,601    market +£42,295   <- big market month
  2025-09: contrib +£1,501    market +£24,206
  2026-02: contrib +£35,501   market +£41,382
  2026-03: contrib +£5,501    market -£38,483   <- correction
  2026-04: contrib +£73,267   market +£21,448
This commit is contained in:
Viktor Barzin 2026-05-07 22:38:31 +00:00
parent f1d69b0a7a
commit 84fd752747

View file

@ -728,7 +728,7 @@
"h": 14,
"w": 24,
"x": 0,
"y": 85
"y": 96
},
"fieldConfig": {
"defaults": {
@ -1702,6 +1702,124 @@
}
]
},
{
"id": 25,
"title": "Monthly contributions vs market gain",
"description": "Each month split into 'new money in' (contributions) and 'market gain' (everything else). Side-by-side bars, so months where market gain exceeds contributions are visually obvious — that's the inflection point where your investments out-earn your savings.",
"type": "barchart",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "wealth-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 75
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"decimals": 0,
"custom": {
"axisPlacement": "auto",
"axisLabel": "",
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "month"
},
"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.7,
"groupWidth": 0.75,
"orientation": "auto",
"showValue": "auto",
"stacking": "none",
"xField": "month",
"xTickLabelRotation": -45,
"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 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)), monthly AS (SELECT date_trunc('month', valuation_date)::date AS month, valuation_date, SUM(total_value) AS nw, SUM(net_contribution) AS contrib FROM daily_account_valuation 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::text AS month, 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 %",
@ -1715,7 +1833,7 @@
"h": 10,
"w": 24,
"x": 0,
"y": 75
"y": 86
},
"fieldConfig": {
"defaults": {