docs(adr): 0013 — stocks provider evaluation, AV retained as bascule target (#196)

This commit is contained in:
maximus 2026-05-09 12:40:08 +00:00
parent 9010c04315
commit bc7a0e0231

View file

@ -0,0 +1,217 @@
# ADR 0013 — Évaluation provider stocks : Alpha Vantage retenu comme cible de bascule (override partiel ADR 0011)
- Status: **Accepted**
- Date: 2026-05-07
- Successor of: ADR 0011 (override partiel — la pré-désignation **Tiingo Power** comme cible de bascule devient invalide ; **Alpha Vantage Premium** la remplace)
- 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. Un trigger ADR 0011 est de fait actif. Le feature stocks est non-fonctionnel en production.
**Décision encadrante (le présent ADR ne la modifie pas)** : on reste dans l'esprit MVP de l'ADR 0011 — **0 $ de cash burn tant que le produit n'a pas validé son marché**. La bascule vers un provider payant est repoussée jusqu'à un trigger plus net (1ère licence payée, OU 1ère plainte client active, OU saturation des plaintes "stocks cassé"). Le scope du présent ADR est de **valider empiriquement quel provider sera la cible de bascule** quand un trigger réel se déclenchera, **pas** de déclencher la bascule maintenant.
Avant de figer mécaniquement Tiingo Power comme dans 0011, l'évaluation a été élargie à 3 providers — Alpha Vantage, Tiingo, Polygon — pour potentiellement override la pré-désignation 0011 si un autre provider domine.
## Phase 1 — Recherche documentaire (sources publiques uniquement)
3 sous-agents WebSearch ont produit une synthèse 6-axes par provider. 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 pour cas commercial | ❌ 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 |
| **Profondeur historique daily** | 20+ ans | 30+ ans US | 5 ans Starter |
| **Free tier viable pour dev** | ✅ 25 req/jour, 5 req/min — OK pour valider l'intégration | ⚠️ 1000 req/jour mais ToS interdit le commercial sans Commercial plan | ⚠️ 5 req/min, EOD only, ToS interdit le commercial sans Business |
**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 (Power = internal-use only). Notre cas d'usage cible (proxy multi-tenant) forcerait 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 et offre un free tier exploitable pour valider l'intégration en dev.
## 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` partout.
## Decision
**Override partiel de l'ADR 0011 — uniquement le provider de bascule désigné** :
- L'ADR 0011 reste en vigueur sur tout le reste : Yahoo best-effort en prod, garde-fous (badge UX, circuit breaker, quota 200 req/jour/licence, saisie manuelle toujours active), triggers de migration inchangés.
- **Le provider de bascule désigné passe de Tiingo Power à Alpha Vantage Premium $49.99/mo (75 rpm)** quand un trigger ADR 0011 se déclenchera réellement.
- **Aucune bascule immédiate.** Yahoo best-effort reste en prod tant qu'aucun trigger réel (1ère licence payée, plainte client formelle, saturation des incidents) ne justifie le cash burn.
### Pourquoi Alpha Vantage plutôt que Tiingo (pré-désignation 0011)
1. **Tiingo Power est invalide pour notre cas** : "internal consumption only" dans le ToS. Notre proxy multi-tenant force Tiingo en plan Commercial ~$500/mo + redistribution license — ~10× plus cher que la pré-désignation 0011 ($10/mo). Le rapport coût/bénéfice supposé par 0011 ne tient plus.
2. **Alpha Vantage Premium $49.99/mo** est le palier le moins cher qui (a) couvre le besoin technique avec marge (75 rpm × 1440 = ~108k req/jour, vs cible 10k/jour), (b) inclut `TIME_SERIES_DAILY_ADJUSTED` confirmé empiriquement comme premium-gate, (c) couvre le TSX nativement.
3. **Drop-in Yahoo via `.TO` natif** — découverte Phase 2 majeure : 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`. C'est l'argument décisif pour la bascule rapide quand elle sera déclenchée — délai d'implémentation ~quelques heures, pas quelques jours.
4. **ToS en zone grise = négociable au moment voulu** : pas d'interdiction explicite (vs Polygon Individuals qui interdit, vs Tiingo Power qui interdit, vs Yahoo qui interdit). Email à `support@alphavantage.co` peut être envoyé au moment du déclenchement, pas avant.
### Polygon écarté
Polygon est techniquement supérieur (data quality, "unlimited" rpm sur Starter $29) mais **disqualifié seul par l'absence de couverture TSX**. Une stratégie hybride Polygon US + AV CA serait plus complexe et plus chère pour un bénéfice marginal vs AV seul.
### État du free tier AV (statu quo dev)
La clé free tier obtenue pendant l'évaluation reste active pour :
- **Dev / smoke test continu** : valider l'intégration en local avant tout déploiement payant.
- **Smoke test périodique de la cible de bascule** : 1× par mois, 5-10 calls pour vérifier que la convention `.TO` fonctionne toujours, que les premium-gates n'ont pas changé, que `support@alphavantage.co` n'a pas resserré le free tier.
La clé reste hors-Coolify (jamais déployée en prod), dans `~/.maximus-research-keys/av.txt` côté machine de dev. **À ne pas confondre avec un déploiement de prod** — le free tier 25 req/jour est 400× insuffisant pour servir 50 licences réelles.
## Plan de bascule (déclenché par trigger ADR 0011, pas maintenant)
Quand un trigger réel se déclenchera :
1. **Décision business** : confirmer que la bascule est justifiée vs amender ADR 0011 (rester sur Yahoo + mitigations alternatives).
2. **Email ToS** à `support@alphavantage.co` (draft inclus en annexe ci-dessous) — à envoyer **à ce moment-là**, pas maintenant. Délai de réponse standard 3-5 j ouvrables. Use case précisé : "server-side proxy serving N paying B2B licensees".
3. **Sur réponse positive** : signup Premium $49.99/mo (75 rpm). Clé en var Coolify `ALPHAVANTAGE_API_KEY` (secret). Issue follow-up `feat(api): integrer Alpha Vantage comme provider stocks` créée à ce moment-là.
4. **Sur réponse négative ou zone grise prolongée** : amender cet ADR pour retomber sur Tiingo Commercial ($500+/mo) — financièrement justifiable seulement si l'audience est suffisamment monétisée pour absorber le coût.
5. **Implémentation variante A** (remplacement direct, switch via env var `STOCKS_PROVIDER=alphavantage`). Yahoo retiré du code dans une seconde PR séquencée pour rollback rapide.
6. **Smoke test prod** : `?symbol=AAPL` et `?symbol=SHOP.TO` doivent renvoyer 200 + prix non-stale.
7. **Bascule + monitoring 7 jours**.
8. **Cleanup** : `~/.maximus-research-keys/` supprimé.
## Garde-fous obligatoires (mirror ADR 0011, applicables à AV au moment de la bascule)
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é, marge ample pour 50 licences.
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 — applicable au moment de l'implémentation)
Le module `alphaVantageProvider.ts` doit gérer **5 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 déclenchée par la bascule.
## Consequences
### Positives
- **0 $ de cash burn maintenu** — l'esprit MVP de l'ADR 0011 est préservé. Pas de bascule prématurée à un provider payant.
- **Cible de bascule validée empiriquement**`.TO` natif, TSX confirmé, mutual funds OK, format API simple. Au moment du trigger, la bascule prendra des heures, pas des jours.
- **Override propre de la pré-désignation Tiingo** — la décision 0011 ne s'auto-déclenche pas mécaniquement vers un provider mal calibré.
- **Email ToS reporté** — pas d'effort gaspillé tant qu'il n'y a pas d'enjeu réel.
### Négatives / risques actés
- **Yahoo reste cassé en prod** — feature stocks non-fonctionnel jusqu'au déclenchement du trigger ou résolution Yahoo (improbable). Acceptable tant qu'aucun client payant ne se plaint.
- **Adjusted close = premium endpoint** chez AV : confirmé empiriquement. Au moment de la bascule, le tier Premium $49.99 sera nécessaire (pas de chemin gratuit pour `TIME_SERIES_DAILY_ADJUSTED`).
- **Indices broad-market non couverts via `GLOBAL_QUOTE`** : SPX, ^GSPC, GSPTSE retournent objet vide. Hors-scope tant que la roadmap ne demande pas d'indices ; sinon ETF proxy (`SPY`, `XIC.TO`).
- **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. Mitigation = regex de masking pino.
- **ToS en zone grise jusqu'à confirmation écrite future** : risque de suspension de clé sans préavis si AV détecte un pattern multi-tenant. Probabilité faible à petite échelle, à monitorer.
- **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.
## Triggers de bascule (rappel ADR 0011, inchangés)
- 1+ incidents IP-ban Yahoo / mois pendant 2 mois consécutifs, OU
- 30 %+ requêtes stocks tombent en `service_degraded` sur 7 jours, OU
- Plainte légale formelle de Yahoo / Verizon Media, OU
- (nouveau) 1ère licence payée active OU 1ère plainte client formelle sur le feature stocks.
Le 4ème trigger est ajouté pour aligner la décision avec la réalité business : tant qu'il n'y a pas de licence payée ou de plainte active, le 0 $ recurring l'emporte sur le service-quality.
## Suivi
- ADR à reviewer dans 6 mois (2026-11-07) ou plus tôt si :
- Trigger de bascule déclenché (réévaluer Yahoo vs AV en fonction du contexte du moment),
- AV resserre le free tier au point que le smoke test mensuel devient impossible,
- Yahoo redevient stable (improbable mais possible).
- Métriques à tracker dans le log applicatif maximus-api : `yahoo_success_rate_7d`, `yahoo_breaker_open_count_30d`, `crypto_provider_distribution`, `paid_licenses_active_count`.
- Smoke test mensuel AV free tier : entrée TODO dans le calendrier ou cron `claude` skill.
## Annexe — Draft email à `support@alphavantage.co` (à envoyer au moment de la bascule, pas maintenant)
```
Subject: Commercial use authorization — server-side proxy for ~N 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 ~N paying B2B licensees
- Delayed/EOD US equities (NYSE/NASDAQ/AMEX) and Canadian equities (TSX via .TRT/.TO)
- Mutual funds (limited)
- ~Y 000 requests/day total, ~Z req/min peak
- 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
```
Réponse écrite à archiver dans `simpl-resultat/docs/adr/0013-attachments/alphavantage-tos-confirmation-YYYY-MM-DD.txt` une fois reçue.
## 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 sur le provider de bascule désigné)
- `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`