React + FastAPI starter to track stock positions, log hourly price snapshots, and view gains/losses.
Run the prebuilt image:
docker pull meanouar/followstocks
docker run --name followstocks -p 8000:8000 -p 4173:4173 meanouar/followstocksSet frontend API URL at runtime (useful for reverse-proxy/domain deployments):
docker run --name followstocks \
-p 8000:8000 -p 4173:4173 \
-e API_BASE_URL="https://api.your-domain.com" \
meanouar/followstocksPersist SQLite data locally:
touch data.db
docker run --name followstocks \
--rm \
-p 8000:8000 -p 4173:4173 \
-v "$(pwd)/data.db:/app/backend/data.db" \
meanouar/followstocksBuild the image yourself:
docker build -t meanouar/followstocks .Frontend: http://localhost:4173
API: http://localhost:8000
Requirements: Python 3.11+, virtualenv recommended.
With Pipenv:
cd backend
pipenv install
pipenv run uvicorn app.main:app --reloadAPI runs on http://localhost:8000.
Playwright browser binaries are required for the Boursorama flow:
cd backend
pipenv run playwright install chromiumGET /healthsimple readiness check.POST /auth/register{email, password, name?}register and receive a JWT.POST /auth/login{email, password}receive a JWT.GET /auth/meget the current user (requires Bearer token).POST /holdings{symbol, shares, cost_basis, currency?}create a holding (duplicate symbols allowed).GET /holdingslist holdings with computed stats (market value, gain %, hourly change).PUT /holdings/{id}partial update for shares/cost/currency/symbol.DELETE /holdings/{id}remove a holding and its snapshots.POST /prices{holding_id, price, recorded_at?}add a price snapshot;recorded_atdefaults to now.GET /prices/{holding_id}?limit=24fetch recent snapshots for a holding.GET /portfolioportfolio summary + holdings stats.GET /search?q=uses Yahoo Finance search for symbol lookup (used for autocomplete).GET /quotes/yfinance?symbol=fetches latest price via Yahoo Finance.GET /integrations/boursorama/sessioninspect whether a saved Boursorama Playwright session exists for the current user.POST /integrations/boursorama/cash/previewreuse the saved Boursorama session and extract cash-like balances from the authenticated page.POST /integrations/boursorama/cash/syncextract balances from Boursorama and sync them into local account liquidity.GET /analysis/cac40?metric=analyst_discount|pe_discount|dividend_yieldCAC40 undervaluation ranking.- Background task (enabled by default) that refreshes prices hourly using Yahoo Finance symbols and stores them as snapshots. Control with
AUTO_REFRESH_ENABLED(true/false) andAUTO_REFRESH_SECONDS(default3600).
Holdings, prices, and portfolio endpoints require Authorization: Bearer <token>.
Hourly change is computed as the delta between the latest snapshot and the most recent snapshot at least one hour earlier (or the previous snapshot if none are that old). Currency is restricted to USD or EUR (default USD).
This flow does not store your Boursorama password in the app. It opens a real Chromium window, lets you complete the login and MFA manually, then saves Playwright storage state (cookies + local storage) under backend/private/boursorama/.
Capture a session for a FollowStocks user id:
cd backend
pipenv run python -m app.boursorama_cash_sync auth --user-id 1Preview extracted balances from the saved session:
cd backend
pipenv run python -m app.boursorama_cash_sync fetch --user-id 1Sync those balances into local account liquidity:
cd backend
pipenv run python -m app.boursorama_cash_sync sync --user-id 1The extractor is heuristic because Boursorama can change the authenticated DOM. If you need to inspect what the parser sees, dump the normalized page text:
cd backend
pipenv run python -m app.boursorama_cash_sync fetch --user-id 1 --dump-text /tmp/boursorama-lines.jsonNotes:
- Matching is done by account name. With sync enabled, missing accounts are created automatically unless you pass
--no-create-missing. - Only EUR balances are synced into account liquidity because the account model does not store a cash currency.
- If the saved session expires, rerun the
authcommand.
There is also a higher-level importer that can create or reuse a FollowStocks user, capture the Boursorama session, then import:
- every detected Boursorama account into FollowStocks
accounts - securities inside PEA / PEA-PME / ORD / CTO style accounts into FollowStocks
holdings - savings-style products like assurance vie and livrets into FollowStocks
placements
Create a new FollowStocks user, open the Boursorama login, then import:
cd backend
pipenv run python -m app.boursorama_import \
--email you@example.com \
--password changeme123 \
--name "Your Name" \
--capture-sessionReuse an existing FollowStocks user and an already-saved Boursorama session:
cd backend
pipenv run python -m app.boursorama_import --user-id 1Useful options:
--headedkeeps the import browser visible after the session is already saved.--dump-dir /tmp/boursorama-importstores HTML and normalized text for the landing page and each parsed account page to help tune the heuristics.--reset-user-datadeletes the target user's current portfolio data before importing, but keeps the user account and saved Boursorama session.--engine browser-useswitches the discovery phase to abrowser-useagent that starts fromhttps://clients.boursobank.com/, uses the saved authenticated session, and is instructed to parsediv.c-panel__bodypanels first before opening detail pages only when needed.--browser-use-max-steps 20limits how far thebrowser-useagent is allowed to explore.
Optional browser-use setup:
cd backend
pipenv install browser-use
pipenv run python -m app.boursorama_import --user-id 1 --engine browser-use --headed --dump-dir /tmp/boursorama-importIf your default OPENAI_MODEL is not accepted by browser-use, set a dedicated override such as BOURSORAMA_BROWSER_USE_MODEL=gpt-4.1-mini.
Notes:
- The importer is heuristic because the authenticated Boursorama DOM can change.
- If the saved session is expired, the importer now reopens the Boursorama login for the same FollowStocks user and retries once automatically.
- If detailed holdings inside a security account cannot be parsed, the importer falls back to one aggregate placement for that account instead of dropping the value entirely.
- The
browser-useengine is opt-in and requires a working LLM configuration (OPENAI_API_KEYor Azure OpenAI settings). It also disablesbrowser-usetelemetry by default for this bank-import path. - The importer captures daily history after import so the portfolio view reflects the imported state immediately.
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"changeme123"}'
# Copy the access_token from the response and set it here:
TOKEN="PASTE_TOKEN_HERE"
curl -X POST http://localhost:8000/holdings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"symbol":"AAPL","shares":10,"cost_basis":150}'
curl -X POST http://localhost:8000/holdings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"symbol":"MSFT","shares":5,"cost_basis":280}'
# Copy the holding ids from the responses and use them here:
curl -X POST http://localhost:8000/prices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"holding_id":1,"price":180}'
curl -X POST http://localhost:8000/prices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"holding_id":2,"price":320}'Requirements: Node 18+.
cd frontend
npm install
npm run dev # opens http://localhost:5173Set VITE_API_BASE in .env if the API is not on http://localhost:8000.
A native iOS starter app is available in ios/.
open ios/FollowStocks.xcodeprojThen run the FollowStocks scheme in a simulator.
Setup details are in ios/README.md.
- Portfolio summary (invested, market value, unrealized P/L, last-hour change).
- Table of holdings with per-position gain %, hourly delta, and last price timestamp.
- Forms to add holdings and log hourly price snapshots.
- Add-holding form with live Yahoo Finance symbol suggestions.
- Quick correction form to update shares if you entered them incorrectly.
- Keep the FastAPI app running with a process manager (systemd, supervisord) or a container.
- Serve the frontend as static files (
npm run build→dist/) behind any web server; proxy/to the built assets and/api(or/) to the FastAPI service.
- Prices are user-supplied; integrate a market data source by posting snapshots hourly.
- SQLite database (
backend/data.db) is created automatically on first run. Back it up if needed. - JWT settings: set
JWT_SECRETand optionallyACCESS_TOKEN_EXPIRE_MINUTES(default 1440). - If you already have a
data.db, add a migration for the newuserstable +holdings.user_id, or delete the file to recreate the schema.