Changelog
This page tracks the notable changes since v4: the headline features, and the
heads-up items that actually matter when you upgrade. The exhaustive,
commit-level list — including every dependency bump — is generated by Release
Please in
CHANGELOG.md.
v4.2 — Config-driven deployment
The Compose stack is driven by .env and one config directory
The stack used to mix the shipped artefact with your data in the same versioned
folder, so switching modes or tuning anything meant editing files that the next
release would overwrite. Two things are now yours, both git-ignored, both
created by make init from a template that upgrades never touch:
.env— everySB_*variable the app supports, under its real name with its real default. Half of them were not exposed at all before, which forced compose edits.- the config directory —
settings.yaml,config.yamlandevents/all live in it, mounted read-only as a single volume at~/.config/SuiviBourseinstead of three file-by-file bind mounts (which made Docker create a directory wherever a file was missing).
docker-compose.yaml is no longer a file you edit — not even to switch modes.
The image tag is pinned by SB_VERSION (:latest left no way to roll back or
to hold a major), ports and the container-name prefix are variables so two
stacks can coexist, log rotation and an app healthcheck are set, and Grafana
keeps its state in a volume.
One source for the InfluxDB token, and no shipped secret
The admin token had three sources that had to be edited together
(influxdb3-token.json, the Grafana datasource, .env). It now has one:
INFLUXDB_TOKEN in .env, materialised into a token file at container startup
and read by the datasource through $__env{}. The committed token file is gone,
and no token ships with the project any more — make init generates a fresh
one per deployment, and the stack refuses to start while it is empty. Rotating
is a one-line edit.
The mode follows the directory contents
Declaring the mode in your deployment and in settings.yaml was the main way
the two drifted apart. With neither declared, events mode is now selected as
soon as the events source holds a .csv or .xlsx — dropping a broker export
into events/ is the whole switch, removing it switches back. An explicit
SB_CONFIG_MODE or mode: still wins, so nothing changes for deployments that
already state one. Only file extensions count: a README.md or .gitkeep in
events/ never triggers it.
Deploying on Coolify
Most of the stack fits Coolify as-is: every setting is a
${VAR} it can surface in its UI, and there is no .env to supply since it
injects the variables itself. The one part that does not survive contact is the
config directory — Coolify recreates the repository clone on every deployment,
data/ is git-ignored so it is never in that clone, and make init never runs.
Point SB_CONFIG_DIR at an absolute host path, which also keeps your portfolio
outside the deployment's working directory so redeploys cannot touch it. The
guide also flags Coolify's non-standard is_directory: true bind-mount flag,
which plain Docker Compose rejects outright.
Publishing ports is an overlay
docker-compose.yaml no longer binds anything on the host: the three services
talk over the compose network, and docker-compose.expose.yaml carries the
ports: blocks. .env.example chains both through COMPOSE_FILE, so a local
docker compose up -d is unchanged. Drop that line and the same stack comes up
with nothing published — which is what you want behind a reverse proxy, and what
Coolify gets for free since it regenerates its deployment from
docker-compose.yaml alone. It is also the file to trim when only Grafana
should be reachable and InfluxDB should not.
New deployment variables
Set in .env, all optional except INFLUXDB_TOKEN.
| Variable | Default | What it does |
|---|---|---|
SB_VERSION | 4 | App image tag. Pin an exact version for reproducible upgrades and rollbacks. |
SB_CONFIG_DIR | ./data | Config directory mounted into the app — point it at a NAS share or an absolute path. |
COMPOSE_PROJECT_NAME | suivi-bourse | Prefix for container and volume names, to run two stacks side by side. |
GRAFANA_PORT / INFLUXDB_PORT | 3000 / 8181 | Published ports, previously hard-coded in the compose file. |
COMPOSE_FILE | docker-compose.yaml:docker-compose.expose.yaml | Chains the port overlay onto the stack. Comment it out to publish nothing. |
GF_ADMIN_PASSWORD | admin | Grafana admin password. |
Heads-up when upgrading
Existing stacks are on the pre-4.2 flat layout. config.yaml,
settings.yaml and events/ move into the config directory, and the variable
names in .env change (SCRAPING_INTERVAL becomes SB_REGULAR_INTERVAL, and so
on) — step-by-step in
Migrating from a pre-4.2 layout. To
keep the old layout instead, set SB_CONFIG_DIR=. and the whole
docker-compose folder becomes the config directory.
.env is no longer shipped, so it will never be overwritten again — but the
old one was tracked, and a git pull may remove it. Keep a copy before
upgrading. make init skips whatever already exists, so re-running it never
clobbers your configuration.
influxdb3-token.json is gone. The token comes from INFLUXDB_TOKEN in
.env alone. Avoid $ in its value: Grafana expands $VAR inside provisioning
files.
An .env written before the port overlay publishes nothing. The ports:
blocks moved out of docker-compose.yaml, and it is the COMPOSE_FILE line in
.env that pulls them back in — without it Grafana is unreachable on
localhost:3000. Add
COMPOSE_FILE=docker-compose.yaml:docker-compose.expose.yaml to your existing
.env; make init prints the same reminder when the line is missing.
SB_CONFIG_MODE now defaults to unset, not manual. If you keep event files
around while deliberately running in manual mode, the new auto-detection would
flip you to events mode — set SB_CONFIG_MODE=manual explicitly to pin it.
The events: block is now read whichever way the mode was chosen. Selecting
events mode through SB_CONFIG_MODE used to short-circuit the settings.yaml
read, silently ignoring a custom events.source and leaving the file watcher off
despite watch: true. Both now take effect — expect the watcher to actually run
if you had asked for it.
Blank no longer means broken. Compose renders an undefined substitution as an
empty string rather than omitting the variable, so SB_FOO=${FOO} with no FOO
in .env used to reach the container as SB_FOO="" and blow up at boot. Every
SB_* variable now treats blank as unset, and a genuinely malformed value names
itself in the error.
v4.1 — Accounts, performance & market-aware scraping
First-class accounts (opt-in)
Declare your brokers/envelopes (PEA, CTO, …) in settings.yaml and tag every
event line with an account. Holding the same symbol in several accounts no
longer overwrites itself: every point written to portfolio_metrics carries an
account tag, and each account gets its own cash ledger fed by the new
DEPOSIT / WITHDRAWAL cash events (BUY, SELL and DIVIDEND move cash too).
Two new measurements hold the per-account and global picture: account_metrics
(cash balance, holdings value, total value, net contributed) and
portfolio_totals.
→ Accounts (opt-in) · Upgrading to accounts
Money-weighted performance
XIRR (annualized money-weighted return), TWR (time-weighted return, base 100) and absolute gain are computed from your event timeline and the stored price history, per account and for the whole portfolio, and shipped in a new Accounts Grafana dashboard.
External flows (deposits, withdrawals, granted shares) count as contributions; internal flows (buys, sells, dividends, fees) count as performance — so paying money in never looks like a gain.
Market-aware, per-symbol scraping
The single global scrape loop is gone. SuiviBourse now runs one
self-rescheduling job per held symbol, and each one polls only while its
market is open (REGULAR), then sleeps until the next open. Closed markets write
nothing at all — which is what kills the dense closed-market points that were
fragmenting InfluxDB's Parquet files, and cuts the matching Yahoo! Finance calls.
Also in the box: a 30 s anti-herd jitter so an exchange's holdings don't stampede at the opening bell, a dead-ticker backoff for symbols that keep returning no price, and the performance recompute moved to its own gated job that only runs when something actually changed.
Bidirectional backfill
Backfill used to walk only backward, toward your first purchase. It now also walks forward: if the app was stopped, crashed, or the host slept through market hours, the missed session is fetched back on the next cycle instead of staying a permanent hole. Weekend and holiday windows come back empty from Yahoo! Finance and correctly stay gaps.
Price-freshness sonde
A diagnostic that catches the failure the gap-fill above cannot see: a writer
that keeps running and keeps writing, but persists a frozen price while the
market moves. It logs a WARNING and raises the sb_price_staleness gauge, and
clears itself when the writer recovers.
New environment variables
All optional — leaving every one of them unset reproduces the previous behavior.
| Variable | Default | What it does |
|---|---|---|
SB_REGULAR_INTERVAL | 120 | Poll interval while a symbol's market is open. Replaces SB_SCRAPING_INTERVAL. |
SB_PERF_INTERVAL | 120 | Interval of the gated performance-recompute job. |
SB_DYNAMIC_EXECUTOR_POOL | false | Auto-size the scheduler thread pool from the largest same-exchange cohort. |
SB_EXECUTOR_POOL | 10 | Fixed thread-pool size when auto sizing is off. |
SB_STALENESS_HORIZON | 900 | Price-freshness sonde horizon (seconds); 0 disables it. |
Heads-up when upgrading
SB_SCRAPING_INTERVAL is deprecated. It is still honored as a fallback for
SB_REGULAR_INTERVAL (with a warning); if both are set, the new one wins. Rename
it at your leisure —
details.
Your charts will show weekend and holiday gaps. Closed markets write no point, so candlestick, volume and time-series panels are blank across a closed market instead of showing flat fake candles. Stat tiles still resolve the last close, provided the dashboard range covers a few days (the shipped defaults do). A fully-closed day writes no performance point either — by design.
External Prometheus alerts need a look. A "no fresh samples" alert on
sb_share_price will now fire over a normal weekend: widen its window, or switch
it to the sb_price_staleness gauge, which already tells a shut market apart
from a stuck writer.
The sb_* gauges gained an account label. A query that aggregated without
it now splits per account — restore the old shape with
sum without(account) (…).
Points written before the upgrade have no account tag. They read back as
NULL; use COALESCE(account, 'default'), which the shipped dashboards already
do. Nothing is migrated or rewritten — InfluxDB 3 Core cannot delete or update
rows, only whole tables.
Declaring accounts makes the account column mandatory on every event line,
including cash events. Not declaring any account keeps everything under
default, exactly as before.
The performance series are events-mode + opt-in-accounts only, and
portfolio_totals is written only when all accounts share a single currency (FX
conversion is out of scope).
Two optional, manual purges exist if the visual seam across the upgrade bothers you — one for the account tag, one for the pre-upgrade closed-market points. Both destroy real history, are never run automatically, and doing nothing is a perfectly good answer.
v4.0.1
Documentation build fix in CI. No application change.
v4.0 — InfluxDB 3 Core
v4.0 changed the datastore. See the v3 documentation via the version selector in the navbar if you are still running the previous major.
InfluxDB 3 Core replaces Prometheus as the datastore
Prometheus was never meant to hold years of portfolio history. v4 writes every
point to InfluxDB 3 Core (measurement portfolio_metrics), read by Grafana
through an InfluxDB datasource in SQL query mode. INFLUXDB_HOST,
INFLUXDB_DATABASE and a mandatory INFLUXDB_TOKEN are the new connection
settings.
Historical backfill
SuiviBourse reconstructs price history back to your first purchase, so the dashboard shows your portfolio's full evolution instead of starting the day you deployed it. → Historical backfill
Events mode
Describe your portfolio as a list of transactions (BUY, SELL, GRANT,
DIVIDEND) in CSV/XLSX files and let SuiviBourse aggregate positions, weighted
average cost and dividends for you. Manual config.yaml mode still works — the
two modes are mutually exclusive. →
Events mode
Legacy Prometheus endpoint kept
The /metrics endpoint is still exposed (default port 8081) for
pre-v4 deployments, as a snapshot of the current values only — no history,
no backfill. → Legacy Prometheus endpoint
Heads-up when upgrading from v3
History is not migrated. What is in Prometheus stays in Prometheus; InfluxDB starts empty and backfill rebuilds the price history from Yahoo! Finance. Metrics that only exist on live scrapes (dividend yield, P/E ratio, market cap) are not reconstructed.
Grafana needs a new datasource (InfluxDB, SQL query mode) and the v4 dashboard; the v3 Prometheus dashboard does not apply.
INFLUXDB_TOKEN is required — the app will not start without it.