Skip to main content
Version: v4

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 — every SB_* 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 directorysettings.yaml, config.yaml and events/ all live in it, mounted read-only as a single volume at ~/.config/SuiviBourse instead 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.

Docker Compose

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.

InfluxDB token

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.

Mode selection priority

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.

Deploying with Coolify

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.

Not publishing ports at all

New deployment variables

Set in .env, all optional except INFLUXDB_TOKEN.

VariableDefaultWhat it does
SB_VERSION4App image tag. Pin an exact version for reproducible upgrades and rollbacks.
SB_CONFIG_DIR./dataConfig directory mounted into the app — point it at a NAS share or an absolute path.
COMPOSE_PROJECT_NAMEsuivi-boursePrefix for container and volume names, to run two stacks side by side.
GRAFANA_PORT / INFLUXDB_PORT3000 / 8181Published ports, previously hard-coded in the compose file.
COMPOSE_FILEdocker-compose.yaml:docker-compose.expose.yamlChains the port overlay onto the stack. Comment it out to publish nothing.
GF_ADMIN_PASSWORDadminGrafana admin password.

Environment variables

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.

Reading the dashboard

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.

Market-aware scraping

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.

Historical backfill

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.

Price-freshness sonde

New environment variables

All optional — leaving every one of them unset reproduces the previous behavior.

VariableDefaultWhat it does
SB_REGULAR_INTERVAL120Poll interval while a symbol's market is open. Replaces SB_SCRAPING_INTERVAL.
SB_PERF_INTERVAL120Interval of the gated performance-recompute job.
SB_DYNAMIC_EXECUTOR_POOLfalseAuto-size the scheduler thread pool from the largest same-exchange cohort.
SB_EXECUTOR_POOL10Fixed thread-pool size when auto sizing is off.
SB_STALENESS_HORIZON900Price-freshness sonde horizon (seconds); 0 disables it.

Environment variables

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

Breaking release

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.