Skip to content

Onboarding développeur

Bienvenue. Ce document est conçu pour qu'un nouvel arrivant soit productif sur PortfolioAI en moins d'une heure — depuis le clone du repo jusqu'à un dossier ticker complet avec un narratif LLM. Si tu cherches un détail technique précis (commandes, structure, conventions), va plutôt dans developpement.md. Ici on raconte une histoire, pas une référence.


Ce que fait l'app, en 60 secondes

PortfolioAI est un outil d'intelligence de marché par ticker. Pour chaque action / ETF / crypto que tu détiens (importé depuis Wealthsimple) ou que tu surveilles, le backend :

  1. Récupère les données de marché chez Twelve Data (quote courante + 1 an d'historique OHLC).
  2. Calcule les indicateurs techniques côté serveur, en Kotlin pur, testé unit (RSI, MA50/MA200, momentum, drawdown 52w, etc.).
  3. Demande au LLM (Claude par défaut) de rédiger un court narratif à partir de ces indicateurs : {summary, sentiment, keyPoints[3..5]}. Le LLM est un rédacteur, pas un décideur — il décrit ce que les chiffres montrent, il ne prédit pas et ne donne pas d'ordre d'achat / vente.
  4. Persiste un snapshot du résultat (prix + indicateurs + narratif + modèle utilisé) pour pouvoir relire dans 6 mois ce que disait l'IA en regardant quoi.

Le portefeuille est lecture seule dans l'UI : la seule façon de l'alimenter est l'import d'un CSV Wealthsimple.


Installation en 5 minutes

Prérequis

  • Docker Desktop (running)
  • Java 21 (Temurin recommandé, on utilise 21.0.x)
  • Node 24 + npm
  • Tilt — brew install tilt sur macOS, voir tilt.dev pour Linux/Windows

Premier lancement

git clone <repo-url>
cd trade
tilt up

Tilt ouvre son UI sur http://localhost:10350/ et démarre quatre services :

Service Rôle URL
postgres BDD principale (Flyway applique les migrations au boot du backend) localhost:5432
ollama LLM local (backup, optionnel) http://localhost:11434
backend Spring Boot, profil local http://localhost:8080
frontend Angular dev server http://localhost:4200

Le premier démarrage prend 2-3 minutes : Gradle télécharge les dépendances et npm install s'exécute. Les démarrages suivants sont quasi instantanés grâce au hot-reload de Tilt.

Auth en dev (Phase 4) : par défaut, le backend démarre en mode local-no-auth — un user fake ADMIN (dev@local.test) est seedé au boot, et Spring Security est bypassed. Tu peux directement ouvrir le frontend et tout est accessible. Pour tester le vrai flow OAuth Google contre localhost (rare en onboarding), voir developpement.md > Modes d'authentification — il faut un OAuth Client ID Google Cloud Console + quelques variables .env. Pas nécessaire pour démarrer.

Conflit de port avec un autre service local

Si tilt up échoue sur « port already allocated » (le plus courant : un Postgres déjà installé localement occupe le 5432), la procédure .env qui surcharge un seul port est documentée dans developpement.md (section Conflit de port) — table des 4 variables, sources qui les lisent, comportement par défaut.

Configurer le LLM

Le backend a besoin d'un LLM pour générer les narratifs. Le fichier backend/src/main/resources/application-local.yml existe déjà (committé depuis 2026-05-18, sans secrets — juste les overrides de comportement dev). Le défaut est llm.provider: ollama. Deux paths pour customiser :

Option A — Claude (recommandé) : qualité narrative supérieure, latence 1-3 s, requiert une clé Anthropic.

  1. Récupère ta clé sur console.anthropic.com.
  2. Path A.1 — Runtime via UI (recommandé) : démarre Tilt, ouvre /settings/configuration > LLM, colle la clé dans le slot SECRET anthropic.api.key, bascule llm.provider sur claude. Aucun reboot, stocké en BDD (app_config), survit aux restarts du backend.
  3. Path A.2 — Boot-time via .env : ajoute ANTHROPIC_API_KEY=sk-ant-... dans le .env à la racine (gitignored). Spring lit la var via relaxed binding (ANTHROPIC_API_KEYanthropic.api.key). Optionnel : bascule llm.provider: claude dans application-local.yml si tu veux Claude par défaut au boot.

Important : ne jamais coller la clé Anthropic directement dans application-local.yml — le fichier est committé, ça serait un leak public. La clé vit en .env (gitignored) ou en BDD via UI.

Option B — Ollama (local, sans clé) : tourne offline, gratuit. C'est le défaut actuel d'application-local.yml. Le modèle qwen2.5:3b répond en 5-10 s sur M1 — qualité narrative en retrait par rapport à Claude mais largement OK pour itérer. Rien à configurer, tilt up suffit.

Pense à télécharger un modèle au premier lancement : ouvre /settings/configuration > LLM, le panneau État Ollama affiche les modèles déjà pull localement et un bouton Pull… ouvre un dialog avec suggestions (qwen2.5:3b ~2 GB est le bon compromis sur M1, ou qwen2.5:7b / llama3.2:3b / phi4-mini si tu veux pousser la qualité). Mistral 7B était le défaut historique mais 30-60 s par appel sur M1 → timeouts fréquents.

Tu peux switcher de l'un à l'autre à tout moment soit en éditant application-local.yml (Tilt redémarre le backend au save), soit — plus pratique — depuis /settings/configuration > LLM qui édite la même clé en BDD sans reboot.


Premier test de l'app

1. Importer un portefeuille démo

Le repo contient deux CSV factices dans docs/data-input/ (DEMO-CELI + DEMO-REER avec 6-7 positions chacun). Va sur http://localhost:4200/import, drag & drop les deux fichiers ensemble. L'app détecte automatiquement les dates dans le nom de fichier (holdings-report-2026-04-15.csv etc.), trie par ordre chronologique, et applique chaque import comme un snapshot.

Pour tes vrais exports Wealthsimple, mets-les dans docs/data-input-local/ (gitignoré) — jamais dans data-input/ qui est versionné.

2. Voir le portefeuille agrégé

Le Dashboard (/) affiche les positions consolidées : ticker, quantité, prix d'achat moyen, valeur de marché, P&L par devise. Chaque ticker est cliquable.

3. Ouvrir un dossier ticker

Clique sur AAPL (ou n'importe quel ticker du portefeuille démo). La page Dossier ticker (/ticker/AAPL) charge :

  • En-tête avec le prix, la plage 52 semaines, le nom de l'instrument
  • Une courbe SVG inline du prix sur 1 an
  • 10 chips d'indicateurs avec color-coding (RSI, MA50/MA200, momentum 30j/90j, perfs, drawdown, distance vs MA, volume relatif)

4. Switcher la langue

Dans le header en haut à droite, l'icône drapeau ouvre un menu FR / EN. Le choix est persisté dans localStorage et appliqué à toute l'UI (l'attribut <html lang> est aussi mis à jour pour l'accessibilité). Si ton navigateur est en français, l'app démarre en FR par défaut, sinon en EN.

Les fichiers de traduction sont dans frontend/public/i18n/<lang>.json. Toute string utilisateur passe par une clé — rien n'est en dur dans les composants.

5. Générer un narratif LLM

Le narratif est généré à la demande (cher en Claude, lent en Ollama). Clique sur Générer / Régénérer — le frontend POST /api/market/ticker/AAPL/narrative, reçoit un job PENDING, et ouvre un EventSource sur /api/market/ticker/AAPL/narrative/jobs/{id}/stream. Le backend pousse une transition de phase à chaque étape du pipeline (LOADING_CONTEXTCALLING_LLMRECEIVED_RAWPARSINGVALIDATINGPERSISTINGDONE) ; le bandeau de progression sous le titre affiche la phase courante avec un compteur de secondes. Une fois DONE reçu, le frontend recharge le snapshot — résumé en 2-3 phrases, badge BULLISH / NEUTRAL / BEARISH, et 3 à 5 bullet points factuels.

Si tu cliques deux fois en moins de 30 minutes sur le même ticker, le 2e clic réutilise le snapshot existant (cache 30 min côté service). Pas de re-prompt LLM, pas de coût supplémentaire.


Switcher les providers

Le projet a cinq providers configurables, chacun avec une vraie implémentation et (sauf Claude) un mock pour dev offline / sans clé.

LLM — llm.provider

Valeur Quand l'utiliser
claude Défaut Phase 1. Qualité narrative nettement supérieure, latence 1-3 s, requiert une clé ANTHROPIC_API_KEY.
ollama Dev offline, sans clé. Défaut local : qwen2.5:3b (3B Instruct, ~2 GB), 5-10 s par narratif sur M1, JSON structuré fiable. Pull depuis /settings/configuration > LLM > Pull… au premier lancement. Pour pousser la qualité au prix de la vitesse : qwen2.5:7b, llama3.2:3b ou phi4-mini. Mistral 7B (ancien défaut) timeout sur M1 — éviter.

Données de marché — market.provider

Valeur Quand l'utiliser
mock Défaut, sans clé requise. MockMarketChartClient génère 260 bars OHLC déterministes par symbole (seed = symbol.hashCode()). Tous les indicateurs se calculent. Symboles réservés : UNKNOWN (404) et RATELIMIT (503) pour exercer les chemins d'erreur UI.
twelvedata Vraie data, défaut prod. REST documenté + apikey, free tier 800 credits/jour, TSX natif. Requiert market.twelvedata.api-key. Crée un compte gratuit sur twelvedata.com puis récupère ta clé sur https://twelvedata.com/account/api-keys. Deux paths pour la poser : (a) UI runtime /settings/configuration > Providers de données (slot SECRET, bouton Tester, persisté en BDD) — recommandé ; (b) Boot-time TWELVEDATA_API_KEY=... dans .env (Spring lit via relaxed binding). Ne jamais coller la clé dans application-local.yml — le fichier est committé.

News par ticker — news.provider

Valeur Quand l'utiliser
mock Défaut, sans clé requise. MockNewsClient génère 4-10 headlines synthétiques déterministes par symbole (templates variés, sources rotation Reuters/Bloomberg/CNBC, ~10 % de symboles "quiet" pour exercer l'empty-state UI). Idéal en itération pour ne pas faire chauffer le quota Finnhub.
finnhub Vraie data. REST + apikey, free tier 60 calls/min sans cap quotidien. Requiert market.finnhub.api-key. Crée un compte gratuit sur finnhub.io/register puis récupère ta clé sur finnhub.io/dashboard. Deux paths pour la poser : (a) UI runtime /settings/configuration > Providers de données (slot SECRET, bouton Tester) — recommandé ; (b) Boot-time FINNHUB_API_KEY=... dans .env. Ne jamais coller la clé dans application-local.yml — le fichier est committé.

Recommandations analystes — analyst.provider

Toggle séparé de news.provider pour pouvoir flipper indépendamment (live news + mock recos pendant l'itération, par exemple). Partage la même clé Finnhub que news.provider quand tu bascules en finnhub.

Valeur Quand l'utiliser
mock Défaut, sans clé requise. MockAnalystClient génère un breakdown synthétique déterministe par symbole (~50 % bullish / ~30 % mixed / ~20 % bearish, drift mois-sur-mois pour une trend line non plate, history sur 6 mois). Symboles réservés : UNKNOWN (404 → empty state), RATELIMIT (503 → inline error), NOTARGET (snapshot avec priceTarget = null pour reproduire la dégradation Finnhub sans flipper de provider).
finnhub Vraie data — /stock/recommendation (breakdown monthly, requis) + /stock/price-target (consensus 12 mois, optionnel — fail-soft à null sur 401/403/5xx parce que sur certains comptes l'endpoint est derrière un paid tier). Partage la même market.finnhub.api-key que news.provider.

Earnings — earnings.provider

Toggle séparé de news.provider et analyst.provider. Partage la même clé Finnhub quand tu bascules en finnhub.

Valeur Quand l'utiliser
mock Défaut, sans clé requise. MockEarningsClient génère 4 trimestres synthétiques déterministes par symbole (EPS dans la bande $0.30–$3.50, surprise ±15 % autour de l'estimé, drift ±8 % d'un trimestre à l'autre) + une next-date 1–60 j en avant. Symboles réservés : UNKNOWN (404 → empty state), RATELIMIT (503 → inline error), NOCALENDAR (snapshot avec nextEarningsDate = null pour reproduire la dégradation Finnhub sans flipper de provider).
finnhub Vraie data — /stock/earnings (4 derniers Q estimate / actual / surprise %, requis) + /calendar/earnings (prochaine annonce sur fenêtre 90 j en avant, optionnel — fail-soft à null sur 401/403/5xx parce que sur certains comptes l'endpoint est derrière un paid tier). Partage la même market.finnhub.api-key que les autres providers Finnhub.

Deux chemins pour modifier ces réglages :

  1. application-local.yml — défaut au boot, recharge au save Tilt. Pertinent pour figer la config d'un environnement.
  2. Page /settings/configuration (icône tune dans le sidenav /settings) — édite en direct les clés API Twelve Data et Finnhub (avec bouton "Tester" qui sonde la clé candidate avant la sauve), le TTL cache Caffeine, et bascule market.provider / news.provider / analyst.provider / earnings.provider mock ↔ live à la volée. Aucune édition YAML, aucun reboot, le prochain dossier ouvert hit le nouveau adapter. Les overrides vivent en BDD (app_config, V4) et prennent le pas sur les défauts YAML.

Pour la liste complète des providers (URLs d'inscription, dashboards, quotas, points d'intégration code) voir providers.md.


Quand ça merde

"Données de marché momentanément indisponibles" sur un dossier ticker

Le provider sélectionné refuse la requête. Diagnostic selon market.provider :

  1. twelvedata — clé manquante (market.twelvedata.api-key vide → message explicite dans les logs) ou quota 800 credits/jour épuisé. Vérifier avec tilt logs backend | grep -i "twelve data". Si quota explosé, bascule temporairement sur mock.
  2. mock — ne devrait jamais arriver sauf si tu testes les symboles réservés RATELIMIT ou UNKNOWN.

Un job narratif reste bloqué PENDING

Le backend a probablement crash ou été hot-reloaded en plein appel LLM. Solution simple : régénère un nouveau job (clic "Régénérer"). Le job mort sera ignoré, un nouveau job DONE écrasera l'affichage. Le backend corrige aussi automatiquement au boot via OrphanedJobCleanupListener (@EventListener(ApplicationReadyEvent)) — tout ticker_narrative_job resté en PENDING au démarrage est flippé en ERROR avec un marqueur explicite, donc un redémarrage suffit à débloquer l'UI sans intervention manuelle.

Migration Flyway échoue au boot

Probablement un checksum désynchronisé après un édit local d'un fichier V*__*.sql déjà appliqué. En dev, le bouton db:reset dans l'UI Tilt drop le schéma puis Flyway rejoue toutes les migrations from scratch. Tu perds tes données locales — recharge les CSV démo après.

Le frontend affiche une page blanche

Vérifie que le backend est Ready dans Tilt (le frontend se contente d'afficher la dernière réponse cache si l'API tombe). Logs : tilt logs backend | tail -50.

Les tests Vitest ne reconnaissent pas describe

Le projet utilise Vitest (pas Karma — ng test invoque le builder Angular et rate la config). Toujours passer par npm run test (full suite watch) ou npx vitest run src/path/to/file.spec.ts (un seul fichier en CI mode).

npm run lint pète sur des règles a11y / inject / array-type

ESLint flat config (frontend/eslint.config.js) est configuré sur Angular ESLint 21 + a11y des templates. Premier réflexe : npm run lint -- --fix auto-corrige les violations triviales (formatage, Array<T>T[], etc.). Pour le reste, les patterns à connaître : - prefer-inject : remplace constructor(private foo: Foo) par private readonly foo = inject(Foo) (le pattern dominant du projet). - click-events-have-key-events + interactive-supports-focus : un <div> ou <li> avec (click) doit avoir role="button" tabindex="0" (keydown.enter). Convertir en <button> est aussi acceptable mais souvent casse le styling. - label-has-associated-control : un <label> doit cibler un input via for= ; si c'est juste un titre de section, utiliser <span class="label">.

Détails ruleset + commandes dans ops.md section ESLint.


Pour aller plus loin

Tu veux comprendre… Va voir
Pourquoi le projet existe, ce qu'on essaie de prouver metier/vision.md
Le découpage des phases et les features à venir metier/fonctionnalites.md, projet/backlog.md (/🚧 uniquement)
Le journal des features déjà livrées (par phase, reverse-chronological) projet/journal-livraisons.md
L'architecture (modules, schéma BDD, décisions techniques) architecture.md
Les conventions de code, commandes Gradle/npm, structure complète developpement.md
Providers externes (Twelve Data, Finnhub, Anthropic, Ollama) — clés, quotas, dashboards providers.md
CI / CD, caching, Detekt, Dependabot, Code Scanning ops.md
Conventional Commits + exemples projet/commit-conventions.md

Ce qui te manque encore après cet onboarding

C'est volontaire — le but est de faire tourner l'app, pas de devenir expert. Les points suivants viendront naturellement quand tu attaqueras une feature :

  • Les conventions hexagonales côté front, groupées sous core/ en 5 sous-dossiers : core/api/<bucket>/<name>.repository.ts = port HTTP (un bucket par module backend), core/api/<bucket>/adapters/<name>.http.ts = adapter HTTP ; core/local/<bucket>/ = port persisté navigateur ; core/app-state/ = services UI signal sans port (theme, language, auth) ; core/http/ et core/router/ = interceptors et guards (Phase 4 auth). Cf. architecture.md.
  • Le pipeline async narratif (ServiceRunner @AsyncExecutor). Cf. architecture.md section analysis/.
  • La règle "tests as documentation" : un fichier de test doit se lire comme une histoire. Cf. .claude/CLAUDE.md.
  • Le flow de commit : un seul auteur (toi), Conventional Commits en anglais, message d'une ligne sans body. Cf. projet/commit-conventions.md.