Picsou Finance
Self-hosted personal finance dashboard Track bank accounts, brokerage, crypto, and net worth — all in one place.
Install / Use
npx skills add Zoeille/picsou-financeInstalls into whichever agent you are using.
README
Picsou
Self-hosted personal finance dashboard
Track bank accounts, brokerage, crypto, and net worth — all in one place.
Getting started · Features · Development · Security
</div>Disclaimer
Picsou is designed for personal, local use.
It stores sensitive financial data (balances, transactions, bank session tokens). It supports multi-member families, optional TOTP 2FA, and audit logging of setup/admin actions, but it has not undergone a professional security audit.
Do not expose it on the public internet. Use it on your local machine or home network behind a firewall. If you choose to expose it, you do so at your own risk.
Features
- Account aggregation — Bank accounts (LEP, PEA, Livret, current), brokerage, crypto wallets, on-chain addresses, debts/loans
- Bank sync — Enable Banking (PSD2/OAuth, 2000+ EU banks).
- Brokerage sync — Trade Republic via WebSocket or CSV import, and Bourse Direct PEA/CTO positions via a local read-only sidecar
- Crypto — Binance exchange sync, on-chain BTC/ETH/SOL address tracking
- Live prices — CoinGecko (crypto), Yahoo Finance (stocks/ETFs)
- Security insight — Per-holding asset-type detection and ETF composition (top holdings, country & sector breakdowns) in the holding detail modal
- Net worth tracking — Historical snapshots, stacked area charts, per-account breakdown
- Savings goals — Targets with deadlines, progress tracking across accounts
- Multi-member family — One admin manages multiple profiles (children, spouse). Per-resource sharing (
NONE/ALL/MANUAL), optional activation links to upgrade a managed profile to a full login. - 2FA + Remember Me — Opt-in TOTP per user, 10 single-use recovery codes, 90-day "Remember Me" cookie with rotating tokens, "Trust this device" to skip TOTP, per-session revocation from settings.
- GDPR data export — Self-service ZIP export (JSON + per-entity CSV) gated by re-authentication, rate-limited to 5/hour.
- Finary import — CSV import or direct API sync
- i18n — English and French
- Dark mode — System/light/dark with flash-free theme switching
Architecture
┌──────────────────┐ ┌───────────────────────┐ ┌────────────┐
│ React Frontend │────▶│ Spring Boot Backend │────▶│ PostgreSQL │
│ (Vite/Bun) │◀────│ (Tomcat :8080) │ │ (:5432) │
└──────────────────┘ └───────────┬────────────┘ └────────────┘
│
┌──────────────┼──────────────┬──────────────┐
▼ ▼ ▼ ▼
Enable Banking CoinGecko Yahoo Finance Trade Republic
(PSD2/OAuth) (crypto) (stocks/ETF) (WebSocket)
- Ports & Adapters —
BankConnectorPort,PriceProviderPort,TradeRepublicPort,BoursoPort, etc. Swap providers without touching business logic. - Two-tier identity —
AppUser(auth) →FamilyMember(domain). Every entity is scoped bymember_id; admins can act on behalf of a managed profile via?memberId=X. - Flyway — Versioned database migrations
- JWT auth — HttpOnly cookies, SameSite=Lax (Safari iOS compatibility), refresh token rotation
- 2FA (TOTP) — Opt-in, with hashed recovery codes and trusted-device cookies
- AES-256-GCM — Mandatory encryption for API secrets at rest (Binance, TOTP secrets, bank session tokens)
- Rate limiting — Bucket4j on login, MFA challenge, sync endpoints, and data export
Tech stack
| Layer | Technology | |-------|-----------| | Backend | Java 21, Spring Boot 3.4, Maven | | Frontend | React 19, TypeScript 5.9, Vite 7, Tailwind v4, Bun | | Database | PostgreSQL 16, Flyway | | Runtime | Docker (Nginx + Spring Boot + supervisor) |
Getting started
Prerequisites
- Docker & Docker Compose v2
- (Optional) An Enable Banking account for bank sync
1. Clone
git clone https://github.com/Zoeille/picsou-finance.git
cd picsou-finance
2. Run (zero-config)
Picsou publishes pre-built, multi-arch (amd64/arm64) images to the GitHub Container Registry, so there is nothing to compile:
| Image | Package |
|-------|---------|
| ghcr.io/zoeille/picsou-finance | picsou-finance — app (frontend + backend) |
| ghcr.io/zoeille/picsou-finance/tr-auth | picsou-finance/tr-auth — Trade Republic auth sidecar |
| ghcr.io/zoeille/picsou-finance/bourse-direct-auth | Bourse Direct login/2FA sidecar |
docker compose -f docker/docker-compose.yml pull # fetch the published images from GHCR
docker compose -f docker/docker-compose.yml up -d
The compose file pins
:latest. To follow the bleeding edge instead, override with:nightly(built on everymainpush), or pin a release such as:1.0.0.Building from source instead of pulling? Run
docker compose -f docker/docker-compose.yml up --build— thebuild:sections are kept for contributors.
On first launch the entrypoint auto-generates JWT_SECRET, CRYPTO_ENCRYPTION_KEY, and POSTGRES_PASSWORD (persisted to the picsou_data volume under /data/.secrets/).
[!IMPORTANT] Planning to sync bank accounts? Set up HTTPS now, before opening the wizard — jump to step 3. Enable Banking refuses plain-HTTP callback URLs, and the wizard permanently stores values derived from the address you open it on. Run it over HTTP and you will have to correct three settings by hand afterwards; run it over HTTPS and they are all derived correctly with nothing to type.
Otherwise, open http://localhost:8080 — the setup wizard walks you through admin credentials, CORS, and (optionally) Enable Banking.
3. HTTPS (decide before the first launch)
Required for bank sync. Enable Banking rejects plain-HTTP callback URLs for PRODUCTION applications, and PRODUCTION is the only mode that lists real banks — so an HTTP-only install can never complete a bank connection. Everything else in Picsou works fine over HTTP.
The stack ships an optional Caddy TLS terminator, off by default so it cannot collide with an ingress proxy you already run.
3a. Choose a hostname
Caddy picks the certificate strategy from PICSOU_DOMAIN alone — there is no issuer to configure:
| PICSOU_DOMAIN | Certificate | What you must do |
|---|---|---|
| A real domain (picsou.example.com) resolving to this host, :80+:443 reachable | Let's Encrypt, publicly trusted | Nothing. Issued and renewed automatically |
| A LAN IP (192.168.1.50) or a .local / .internal name | Caddy's built-in CA | Install its root once per device (3c) |
| picsou.localhost (the default) | Caddy's built-in CA | Resolves only on the Docker host — fine for a smoke test, useless from a phone |
A real domain is worth the effort: it is the only option with no per-device step. It does not need to be publicly reachable for day-to-day use — only during certificate issuance.
3b. Start the stack with TLS
This replaces the up -d from step 2 — it starts the same services plus the proxy, so on a fresh
install run this instead:
# Start from the commented reference file if you don't have a .env yet
[ -f docker/.env ] || cp docker/.env.example docker/.env
# printf's leading \n guards against a .env that lacks a trailing newline —
# plain `echo >>` would concatenate onto the last line and corrupt both values.
printf '\nPICSOU_DOMAIN=picsou.example.com\n' >> docker/.env
docker compose -f docker/docker-compose.yml --profile tls pull
docker compose -f docker/docker-compose.yml --profile tls up -d
[!IMPORTANT] The
pullis not optional on an existing install. Compose will not re-fetch:latestif the image already exists locally, so you would keep an older build that sends HSTS unconditionally — which, combined with the internal-CA certificate from 3c, is precisely the lockout this setup exists to avoid. Building from source? Useup -d --buildinstead.
Note that plain HTTP stays published on :8080 at this point. That is deliberate — it is your way
back in if the certificate is not trusted yet. Close it in 3d, once HTTPS is confirmed working.
Verify before going further:
curl -sk -o /dev/null -w 'https=%{http_code}\n' https://picsou.example.com/
curl -s -o /dev/null -w 'http=%{http_code} -> %{redirect_url}\n' http://picsou.example.com/
Expect https=200 and an http=308 redirect. Now open https://picsou.example.com and run the
setup wizard. It reads your browser's origin, so the callback URL, allowed origins, and secure-cookie
flag all land on the HTTPS values with nothing to type.
Finally, register https://picsou.example.com/sync/callback in the Enable Banking portal — it must
match byte-for-byte, or auth initiation fails with REDIRECT_URI_NOT_ALLOWED.
3c. No domain? Trust the internal CA
Certificate trust cannot be established remotely — each device must be told once that your CA is legitimate. Export the root:
docker compose -f docker/docker-compose.yml cp \
proxy:/data/caddy/pki/authorities/local/root.crt ./picsou-root-ca.crt
Install it per device: Keychain Access (macOS), Settings → Security → Encryption & credentials
(Android), update-ca-certificates (Linux), certmgr.msc (Windows). Firefox keeps its own store —
Settings → Privacy & Security → Certificates.
Only your devices need this. Enable Banking never fetches the callback URL
Related Skills
valuecell
11.0kValueCell is a community-driven, multi-agent platform for financial applications.
QuantDinger
10.4kAI quantitative trading platform for crypto, stocks, and forex with backtesting, live trading, market data, and multi-agent research.vibe-trading ,trading-agents,ai-trader,ai-trading
beanquery-mcp
50Beancount MCP Server is an experimental implementation that utilizes the Model Context Protocol (MCP) to enable AI assistants to query and analyze Beancount ledger files using Beancount Query Language (BQL) and the beanquery tool.
finance-skills
3.1kA collection of skills for AI financial analysis.
