Simpl-Resultat/docs/adr/0013-stocks-provider-evaluation.md
le king fu 67e014b4b6 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
2026-05-07 21:37:16 -04:00

15 KiB
Raw Blame History

ADR 0013 — Évaluation provider stocks : Alpha Vantage retenu (override partiel ADR 0011)

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 :

// 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