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:
parent
9010c04315
commit
67e014b4b6
1 changed files with 198 additions and 0 deletions
198
docs/adr/0013-stocks-provider-evaluation.md
Normal file
198
docs/adr/0013-stocks-provider-evaluation.md
Normal 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`
|
||||
Loading…
Reference in a new issue