docs(adr): 0013 — stocks provider evaluation, Alpha Vantage retained

Override partial of ADR 0011: Tiingo Power pre-designation is invalidated
by 2026 pricing reality (~30 USD/mo, not 10) AND by ToS (Power tier is
internal-use only; multi-tenant proxy requires Commercial 500+/mo plus a
redistribution license).

Alpha Vantage Premium 49.99 USD/mo retained as direct Yahoo replacement,
under suspensive condition of written ToS authorization for commercial
multi-tenant proxy use.

Phase 2 smoke test surfaced a key finding: Alpha Vantage silently accepts
Yahoo-style .TO ticker suffix as alias of .TRT, removing the need for any
mapping table. Drop-in compatible with existing Yahoo provider code.

Refs maximus-api#41
This commit is contained in:
le king fu 2026-05-07 21:37:16 -04:00
parent 9010c04315
commit 67e014b4b6

View file

@ -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/<version>` (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>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`