diff --git a/docs/adr/0013-stocks-provider-evaluation.md b/docs/adr/0013-stocks-provider-evaluation.md new file mode 100644 index 0000000..6ecf05c --- /dev/null +++ b/docs/adr/0013-stocks-provider-evaluation.md @@ -0,0 +1,198 @@ +# ADR 0013 — Évaluation provider stocks : Alpha Vantage retenu (override partiel ADR 0011) + +- Status: **Proposed** (pending written ToS confirmation from Alpha Vantage business support) +- Date: 2026-05-07 +- Successor of: ADR 0011 (override partiel — la pré-désignation Tiingo Power devient invalide) +- Issue: [maximus-api#41](https://git.lacompagniemaximus.com/maximus/maximus-api/issues/41) +- Phase 1 research note: [maximus-api/docs/research/0013-stocks-providers-phase1.md](https://git.lacompagniemaximus.com/maximus/maximus-api/src/branch/issue-41-stocks-providers-research/docs/research/0013-stocks-providers-phase1.md) + +## Context + +L'ADR 0011 (2026-04-26) a adopté Yahoo Finance en best-effort pour les stocks, avec un plan de bascule pré-désigné vers **Tiingo Power (~10 $/mo, 1000 req/jour)** déclenché par triggers (1+ IP-ban/mois × 2 mois consécutifs, ou 30 %+ requêtes en service_degraded sur 7 jours, ou plainte légale). + +Le smoke test 2026-05-04 (issue #25) a confirmé que Yahoo bloque l'IP du VPS OVH de manière stable (`502 provider_unavailable` sur AAPL). Un trigger ADR 0011 est de fait actif. La feature stocks est non-fonctionnelle en production. + +Avant de basculer mécaniquement vers Tiingo Power, la décision (AskUserQuestion 2026-05-05) a été d'élargir l'évaluation à 3 providers — **Alpha Vantage, Tiingo, Polygon** — pour éventuellement override la pré-désignation 0011 si un autre provider domine. + +## Phase 1 — Recherche documentaire (3 axes parallèles, sources publiques uniquement) + +3 sous-agents WebSearch ont produit une synthèse 6-axes par provider (couverture, pricing, ToS, API, TSX, réputation). Synthèse complète dans `maximus-api/docs/research/0013-stocks-providers-phase1.md`. + +Findings critiques : + +| Critère | Alpha Vantage | Tiingo | Polygon | +|---|---|---|---| +| **TSX coverage** | ✅ via `.TRT` / `.TRV` | ❓ non confirmé publiquement | ❌ non couvert | +| **Plan technique min pour 1500 rpm × 10k/jour** | aucun palier public ≥ 1500 rpm (top 1200 rpm @ ~$249.99/mo) | Power suffit techniquement (~$30/mo, 100k/jour) | Starter suffit techniquement ($29/mo "unlimited") | +| **ToS proxy mutualisé pour clients tiers payants** | ⚠️ zone grise, pas de clause explicite, email business requis | ❌ Power = "internal consumption only" → Commercial $500+/mo + redistribution license | ❌ Individuals ToS interdit explicitement → Business négocié obligatoire | +| **HTTP error model** | ⚠️ 200 OK + champ `Note`/`Information` | ⚠️ 200 OK + body non-JSON sur quota | ✅ HTTP 429 standard | +| **Header `Retry-After`** | ❌ non | ❓ non documenté | ❓ non documenté | +| **Profondeur historique daily** | 20+ ans | 30+ ans US | 5 ans Starter | + +**Finding majeur** : la pré-désignation ADR 0011 « Tiingo Power ~10 $/mo » est obsolète sur le prix (réel 2026 ≈ $30/mo) ET invalide sur le ToS. Notre cas d'usage force Tiingo en plan **Commercial** ($500+/mo) avec **redistribution license négociée**. Polygon idem (Business plan, prix non public). Alpha Vantage seul reste en zone grise sans interdiction explicite. + +## Phase 2 — Smoke test live Alpha Vantage (2026-05-07, free tier) + +13 calls réels sur `https://www.alphavantage.co/query` avec une clé free tier. Réponses brutes archivées dans `~/.maximus-research-keys/raw-av.json` (à supprimer après validation de cet ADR). + +| # | Test | Résultat | Verdict | +|---|---|---|---| +| 01 | `GLOBAL_QUOTE` AAPL | $287.44, day 2026-05-07 | Happy path ✅ | +| 02 | `TIME_SERIES_DAILY_ADJUSTED` AAPL | `Information` field — premium endpoint | ⚠️ **Adjusted close = premium $49.99/mo minimum** | +| 03 | `GLOBAL_QUOTE SHOP.TRT` | $152.57 CAD | TSX coverage confirmée ✅ | +| 04 | `GLOBAL_QUOTE RY.TRT` | $247.64 CAD | TSX big caps OK ✅ | +| 05 | **`GLOBAL_QUOTE SHOP.TO`** (Yahoo style) | **$152.57 — alias silencieux de `.TRT`** | 🎉 **Pas de mapping table requis** — drop-in Yahoo | +| 06 | `GLOBAL_QUOTE SHOP` (sans suffixe) | $111.74 — listing US différent | Suffixe nécessaire pour désambiguïsation CA vs US | +| 07 | `GLOBAL_QUOTE XYZAB123` (inconnu) | `"Global Quote": {}` (objet vide) | ⚠️ **Pas de `Error Message`** — détection = empty object | +| 08 | `GLOBAL_QUOTE SPX` | objet racine `{}` vide | ❌ Indices broad-market non couverts | +| 09 | `GLOBAL_QUOTE ^GSPC` | objet racine `{}` vide | ❌ Idem | +| 10 | `GLOBAL_QUOTE VTSAX` (mutual fund) | $176.23, day 2026-05-06 | Mutual funds OK ✅ | +| 11 | `GLOBAL_QUOTE BRK.B` (share class) | $475.08 | Format dot natif ✅ | +| 12 | `SYMBOL_SEARCH keywords=shopify` | 5 résultats incluant `SHOP.TRT` Toronto | Convention `.TRT` confirmée par AV | +| 13 | `TIME_SERIES_DAILY_ADJUSTED` AAPL outputsize=full | `Information` field — premium endpoint | ⚠️ Premium-gate global sur l'historique ajusté | + +**Headers HTTP** : `Retry-After` absent sur les 13 réponses. Toutes en HTTP 200 (confirme la doc — pas de 4xx propres). Content-Type `application/json` sur toutes. + +## Decision + +**Adopter Alpha Vantage Premium (tier minimum $49.99/mo, 75 rpm)** comme provider stocks de remplacement direct de Yahoo, **sous condition suspensive** d'autorisation écrite d'usage commercial multi-tenant obtenue par email à `support@alphavantage.co`. + +Override partiel de l'ADR 0011 : la pré-désignation **Tiingo Power devient invalide** (prix obsolète + ToS internal-use only). Tiingo reste plausible comme fallback secondaire si Alpha Vantage refuse l'usage commercial — mais à un coût ~10× supérieur (Commercial $500+/mo + redistribution license). + +### Justification du choix Alpha Vantage + +1. **Drop-in Yahoo via `.TO` natif** — découverte Phase 2 : AV accepte le suffixe Yahoo `.TO` silencieusement comme alias de `.TRT`. Aucune mapping table à coder. Le code `yahooProvider.ts` existant peut être copié quasi-tel-quel en `alphaVantageProvider.ts`. +2. **Couverture TSX confirmée empiriquement** — `.TRT` (TSX) et `.TRV` (TSX Venture) supportés. Smallcaps non-encore validés mais big caps + symbol search OK. +3. **Mutual funds couverts** (VTSAX testé) — pertinent pour le scope long-terme. +4. **Coût façade le plus bas** des 3 providers viables : $49.99/mo vs $500+/mo Tiingo Commercial vs Polygon Business (non public mais probablement >$100/mo). +5. **ToS en zone grise = négociable** : pas d'interdiction explicite (vs Yahoo qui interdit, vs Polygon Individuals qui interdit, vs Tiingo Power qui interdit). Email à `support@alphavantage.co` peut transformer la zone grise en autorisation écrite. + +### Variantes implémentation + +**Variante A — Remplacement direct (recommandé)** : `STOCKS_PROVIDER=alphavantage` après confirmation ToS. Yahoo retiré du code (ou kept en deadcode commenté pour rollback rapide). + +**Variante B — Fallback chain** : `STOCKS_PROVIDER=alphavantage` primary, `yahoo` secondary sur erreur AV (rate limit / `Information` field / circuit breaker ouvert). Garde Yahoo en best-effort résiduel le temps que le marché confirme la stabilité AV. Plus de code mais plus résilient pendant la transition. + +**Recommandation** : démarrer en variante A. Le risque de bascule unique est limité — si AV devient indisponible, on peut redéployer Yahoo en quelques minutes via env var. La complexité fallback chain n'est justifiée qu'avec 2+ incidents AV avérés. + +### Garde-fous obligatoires (mirror ADR 0011) + +1. **Label UX inchangé** : badge "best-effort" reste sur les catégories stocks. AV est plus stable que Yahoo mais reste un provider tiers — pas de SLA contractuel pour notre cas d'usage à $49.99/mo. +2. **Circuit breaker côté maximus-api** : seuil identique 5 erreurs AV / 60 sec → breaker ouvert 15 min. Notification Telegram/email à `maxime2tremblay@protonmail.com`. +3. **Quota côté maximus-api** : 200 req/jour/licence — inchangé. Avec 75 rpm × 1440 min = 108 000 req/jour de capacité, on a la marge pour 50 licences × 200 req/jour = 10 000 req/jour (~10 % du quota AV). +4. **Saisie manuelle toujours active** : aucun chemin d'erreur ne bloque la saisie d'un snapshot. +5. **Headers stripping rigoureux** : tous les headers entrants supprimés. Vers AV : UA `maximus-api/` (pas besoin de UA browser-like contrairement à Yahoo). Auth via query string `?apikey=` (limitation AV — pas de header support). +6. **Logs** : URL avec `?apikey=` masquée dans les logs Coolify/Traefik via une regex de filtre côté pino logger. + +### Parsing défensif (issu des findings Phase 2) + +Le module `alphaVantageProvider.ts` doit gérer **4 cas distincts** sur HTTP 200 : + +```typescript +// 1. Happy path — body.Global Quote populated +if (body['Global Quote'] && Object.keys(body['Global Quote']).length > 0) { /* ok */ } +// 2. Symbol unknown — body.Global Quote = {} empty object +else if (body['Global Quote'] && Object.keys(body['Global Quote']).length === 0) { /* symbol_not_found */ } +// 3. Premium endpoint blocked — body.Information field with subscribe message +else if (body['Information']) { /* premium_required or rate_limit */ } +// 4. Error — body.Error Message field (param malformed) +else if (body['Error Message']) { /* invalid_request */ } +// 5. Rate limit hit — body.Note field (legacy free tier message) +else if (body['Note']) { /* rate_limit */ } +``` + +Pas de fallback sur HTTP status (toujours 200). Le code de parsing yahoo existant ne couvre pas ces cas — adaptation requise dans la PR follow-up (Phase 4). + +## Plan de migration (séquentiel) + +1. **Email ToS** envoyé à `support@alphavantage.co` (draft inclus en annexe ci-dessous). Délai d'attente standard ~3-5 jours ouvrables. +2. **Sur réponse positive** : signup Premium $49.99/mo (75 rpm), transférer la clé en var Coolify `ALPHAVANTAGE_API_KEY` (secret). +3. **Issue follow-up** `feat(api): integrer Alpha Vantage comme provider stocks` créée → implémentation en variante A. +4. **Smoke test prod** : `?symbol=AAPL` et `?symbol=SHOP.TO` doivent renvoyer 200 + prix non-stale. +5. **Bascule** : `STOCKS_PROVIDER=alphavantage` en var Coolify, redéploiement, monitoring 7 jours. +6. **Cleanup** : `~/.maximus-research-keys/` supprimé. Si variante A : retrait `yahooProvider.ts` du code dans une seconde PR (séquencée pour rollback rapide). + +## Consequences + +### Positives + +- **Coût récurrent bas** : $49.99/mo plancher (avec marge pour upgrade 150-300 rpm si croissance audience). +- **TSX natif via `.TO` Yahoo style** — implémentation triviale, pas de breaking change pour les clients qui passent déjà des tickers `.TO`. +- **Profondeur historique 20+ ans daily** — couvre largement le cas d'usage portefeuille long-terme. +- **Mutual funds couverts** — pertinent pour la roadmap. +- **Symbol search natif** — utile pour l'auto-complétion côté client si jamais. + +### Négatives / risques actés + +- **Adjusted close = premium endpoint** : `TIME_SERIES_DAILY_ADJUSTED` n'est pas dans le free tier. Confirmé empiriquement — `Information` field renvoyé sur free. Le tier Premium $49.99 inclut cet endpoint (à reconfirmer sur la page pricing au moment du signup). Si jamais Premium ne l'inclut pas, upgrade vers tier supérieur ou recalcul côté client à partir des splits/dividends séparés. +- **Indices broad-market non couverts via `GLOBAL_QUOTE` free** : SPX, ^GSPC, GSPTSE retournent objet vide. Hors-scope tant que la roadmap ne demande pas d'indices ; sinon ETF proxy (`SPY`, `XIC.TO`) ou endpoint premium dédié. +- **HTTP 200 sur toutes les erreurs** : parsing fragile, code défensif obligatoire (5 cas distincts à gérer). +- **Pas de `Retry-After` natif** : exponential backoff côté client requis sur détection de `Note`/`Information`. +- **Auth `?apikey=` query string uniquement** : leak risk dans les logs Coolify/Traefik si pas filtré. Mitigation = regex de masking côté pino logger. +- **ToS en zone grise jusqu'à confirmation écrite** : si AV refuse l'usage commercial après email, retomber sur Tiingo Commercial $500+/mo (ADR à amender) ou rester sur Yahoo best-effort en attendant un trigger ADR 0011 plus net. +- **Profondeur smallcaps TSXV non validée** : risque sur ~5-10 % des positions clients (à mitiger en Phase 4 par smoke test sur échantillon représentatif des holdings réels). +- **Free tier érodé historiquement** (500 → 100 → 25 req/jour) : signal qu'AV peut resserrer aussi les paid tiers à terme. Risque budget sur 12-24 mois. + +### Neutre + +- Le code reste `src/services/providers/Provider.ts` parallèle, switch via env var. Pattern déjà en place pour Yahoo, extension triviale. + +## Suivi + +- ADR à reviewer dans 6 mois (2026-11-07) ou plus tôt si : + - AV refuse l'usage commercial après email ToS, + - bascule complète déclenche >5 % erreurs AV / jour pendant 7 jours, + - tier $49.99/mo s'avère insuffisant (>50 % du quota saturé en burst). +- Métriques à tracker dans le log applicatif maximus-api : `alphavantage_success_rate_7d`, `alphavantage_premium_block_count_30d`, `alphavantage_rate_limit_count_30d`, `provider_distribution`. +- Issue de suivi : `feat(api): integrer Alpha Vantage comme provider stocks` à créer en Phase 4 (acceptance criteria détaillés à partir de la section "Parsing défensif" + "Plan de migration"). + +## Annexe — Draft email à `support@alphavantage.co` + +``` +Subject: Commercial use authorization — server-side proxy for ~50 paying B2B licensees + +Hello Alpha Vantage support team, + +I am evaluating Alpha Vantage Premium for a small B2B SaaS use case and would like +to confirm the licensing model in writing before subscribing. + +Use case: +- Server-side proxy (single VPS, single API key) on behalf of ~50 paying B2B licensees +- Delayed/EOD US equities (NYSE/NASDAQ/AMEX) and Canadian equities (TSX via .TRT/.TO) +- Mutual funds (limited) +- ~10 000 requests/day total, ~1500 req/min peak (well under the 75 rpm tier + if we batch, otherwise we would consider a higher tier) +- No client-side redistribution: only the licensee's own portfolio holdings are + fetched, and the response data is consumed by the licensee's own desktop app + (no public-facing data feed, no resale to non-licensees) + +Question: +1. Is this use case authorized under the standard Premium ToS, or do we need + a custom commercial agreement / data onboarding process? +2. Which tier would you recommend for the volume above? +3. Are there any per-end-user fees or exchange data fees I should be aware of + for delayed/EOD US + Canadian equities? + +I would prefer to have written confirmation before subscribing, to comply with +my own internal documentation requirements (the ToS confirmation will be filed +as part of an internal architecture decision record). + +Thanks for your help, +Maxime Tremblay +maxime2tremblay@protonmail.com +``` + +À envoyer après merge de cet ADR (ou en parallèle si ADR mergé en Proposed). Réponse écrite à archiver dans `simpl-resultat/docs/adr/0013-attachments/alphavantage-tos-confirmation-YYYY-MM-DD.txt` une fois reçue, et passer le status de l'ADR de Proposed à Accepted. + +## References + +- [Alpha Vantage — API Documentation](https://www.alphavantage.co/documentation/) +- [Alpha Vantage — Premium API Key (pricing)](https://www.alphavantage.co/premium/) +- [Alpha Vantage — Terms of Service](https://www.alphavantage.co/terms_of_service/) +- [Alpha Vantage — Customer Support](https://www.alphavantage.co/support/) +- [Macroption — Alpha Vantage Symbols (suffixes)](https://www.macroption.com/alpha-vantage-symbols/) +- ADR 0009 — Architecture du proxy +- ADR 0011 — Providers best-effort Yahoo (override partiel) +- `maximus-api/docs/research/0013-stocks-providers-phase1.md` — Synthèse 3-way Phase 1 +- `~/.maximus-research-keys/raw-av.json` — Réponses brutes Phase 2 smoke test (à supprimer après merge) +- Issue Forgejo `maximus-api#41`