API gratuite de données boursières en Python
Interrogez une API gratuite de données boursières avec requests et pandas sous Ubuntu, chargez le JSON dans un DataFrame et affichez le volume moyen sur 20 séances.
Interroger une API gratuite de données boursières depuis Python nécessite une requête HTTP et deux bibliothèques que la plupart des utilisateurs ont déjà : requests pour récupérer le JSON et pandas pour le convertir en DataFrame. Aucune clé API n’est nécessaire. Ce guide s’exécute de bout en bout dans un conteneur Ubuntu vierge et se termine par l’affichage du volume moyen sur 20 séances pour un ticker donné. Le contenu des endpoints est présenté dans le guide API gratuite de données boursières ; cette page explique la partie Python de la même API.
Installer Python, requests et pandas dans un conteneur Ubuntu vierge
Une image Ubuntu standard est fournie sans le module venv de Python, et les versions récentes refusent d’installer des paquets dans l’interpréteur système. Un environnement virtuel permet de contourner ces deux problèmes et fonctionne de la même manière sur toutes les versions actuelles d’Ubuntu. Exécutez ces commandes en tant que root ou préfixez les lignes apt-get par sudo.
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y python3 python3-venv
python3 -m venv .venv
. .venv/bin/activate
pip install "requests>=2.31,<3" "pandas>=2.0,<4"
Les plages de versions sont définies à dessein. Chacune accepte toute version maintenue de la bibliothèque et exclut uniquement une future version majeure dont le comportement est inconnu. Cela permet de réutiliser la même ligne de commande sans problème à mesure qu’Ubuntu fait évoluer sa version de Python par défaut. Aucun élément n’est figé sur une version disponible aujourd’hui. Tout ce qui suit suppose que l’environnement est actif.
Comment appeler gratuitement une API de données boursières en Python ?
Les endpoints de démonstration acceptent des requêtes GET simples, sans clé ni inscription. Le premier script demande l’une des requêtes prédéfinies et affiche la structure de la réponse.
import json
import requests
BASE = "https://ai.strasmore.com/api/demo"
resp = requests.get(BASE, params={"q": "dividend_yield_leaders"}, timeout=30)
resp.raise_for_status()
payload = resp.json()
print(list(payload.keys()))
print(payload["columns"])
print(json.dumps(payload["rows"][0], indent=2))
requests.get construit la chaîne de requête à partir de params, raise_for_status() transforme tout code d’état 4xx ou 5xx en exception, et .json() analyse le corps de la réponse pour en faire un dictionnaire. Réduit à sa structure, ce dictionnaire se présente ainsi :
{
"key": "dividend_yield_leaders",
"label": "...",
"nl": "Which large-cap US stocks currently have the highest dividend yields?",
"sql": "SELECT ...",
"columns": ["as_of", "ticker", "dividend_yield_pct", "price", "market_cap_bn", "price_to_earnings"],
"rows": [{"as_of": "<date>", "ticker": "<symbol>", "dividend_yield_pct": <number>, ...}, ...],
"elapsed": "...",
"source": "Strasmore Research",
"more": "..."
}
Deux clés sont importantes pour pandas. columns indique les champs dans leur ordre, et rows contient un objet JSON par enregistrement, avec ces noms comme clés. La clé sql correspond exactement à la requête qui a produit les chiffres. Elle est renvoyée avec chaque réponse, ce qui rend les données auditables plutôt que opaques. Une requête GET vers /api/demo/catalog liste toutes les clés prédéfinies ainsi que l’endpoint SQL utilisé ensuite.
Comment charger le JSON dans un DataFrame pandas ?
Les requêtes préparées répondent à des questions précises. Les données journalières pour le ticker de votre choix sont accessibles via l’endpoint SQL sans inscription à l’adresse /api/demo/sql. Celui-ci accepte une requête en lecture seule dans le paramètre sql et renvoie la même paire columns et rows. En septembre 2026, ses limites sont indiquées dans chaque réponse : 500 lignes, 20 secondes, un an d’historique, sans clé. La API SQL gratuite avec une clé relève le plafond à 100 requêtes par jour et permet d’accéder à un historique plus long. Le code de requête reste identique.
Deux garde-fous dans le SQL rendent la moyenne cohérente. Une table mise à jour pendant la séance peut contenir plusieurs lignes pour la journée en cours. Le volume de cette journée est partiel tant que le marché est ouvert. La requête s’arrête à la veille avec date < today() et regroupe les éventuelles lignes en double pour une date avec GROUP BY date et max(volume). Si vous n’avez pas encore étudié la construction d’une barre journalière à partir du flux de transactions, la construction des barres OHLCV explique l’origine de ces lignes.
import time
import requests
import pandas as pd
SQL_URL = "https://ai.strasmore.com/api/demo/sql"
TICKER = "AAPL"
SQL = f"""
SELECT date, max(volume) AS volume
FROM stocks_daily_aggs
WHERE ticker = '{TICKER}'
AND date < today()
GROUP BY date
ORDER BY date DESC
LIMIT 20
"""
def fetch(sql, attempts=4):
"""GET the SQL endpoint. Back off on 429 and 5xx; stop on any other 4xx."""
delay = 2.0
for attempt in range(1, attempts + 1):
resp = requests.get(SQL_URL, params={"sql": " ".join(sql.split())}, timeout=30)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429 or resp.status_code >= 500:
try:
wait = max(1.0, float(resp.headers.get("Retry-After")))
except (TypeError, ValueError):
wait = delay
print(f"HTTP {resp.status_code}; waiting {wait:.0f}s (attempt {attempt} of {attempts})")
time.sleep(wait)
delay *= 2
continue
raise SystemExit(f"HTTP {resp.status_code}: {resp.text[:400]}")
raise SystemExit("gave up after repeated 429 or 5xx responses")
payload = fetch(SQL)
df = pd.DataFrame(payload["rows"], columns=payload["columns"])
df["date"] = pd.to_datetime(df["date"])
df["volume"] = pd.to_numeric(df["volume"])
df = df.sort_values("date").reset_index(drop=True)
print(df.to_string(index=False))
avg_20 = df["volume"].mean()
first, last = df["date"].iloc[0], df["date"].iloc[-1]
print(f"{TICKER} 20-session average volume: {avg_20 / 1e6:.1f}M shares "
f"({first:%Y-%m-%d} to {last:%Y-%m-%d}, {len(df)} sessions)")
pd.DataFrame(rows, columns=columns) construit directement la table à partir de la liste d’objets, et le passage de columns conserve l’ordre des champs de l’API. Deux conversions suivent : les dates reçues sous forme de chaînes deviennent de véritables timestamps, et volume est converti en nombre au cas où une réponse le sérialiserait sous forme de texte. Le tri de la date la plus ancienne à la plus récente place le DataFrame dans l’ordre attendu par un graphique. df["volume"].mean() calculée sur exactement vingt lignes correspond à la moyenne sur 20 séances. La dernière ligne l’affiche en millions, avec la période couverte.
Que mesure réellement le volume moyen sur twenty days ?
Le volume moyen lisse une série quotidienne volatile. Le nombre de titres échangés lors d’une séance peut varier fortement en fonction des rebalancements d’indices, des expirations d’options, des publications de résultats et des informations de marché. Twenty sessions correspondent à environ un mois calendaire. Cette période est assez longue pour atténuer ces pics, tout en restant assez courte pour suivre une évolution de l’activité du titre. Le panneau ci-dessous calcule la même statistique que celle affichée par le script, à partir du même tableau quotidien et sur les derniers mois. Les séances brutes et la moyenne mobile sont présentées côte à côte.
| séance | volume (millions) | moyenne 20 j (millions) |
|---|---|---|
| 2026-08-17 | 38.2 | 51.9 |
| 2026-08-18 | 53.4 | 52.5 |
| 2026-08-19 | 50.5 | 53 |
| 2026-08-20 | 41 | 53.1 |
| 2026-08-21 | 46.9 | 53 |
| 2026-08-24 | 34.7 | 52.3 |
| 2026-08-25 | 25.9 | 51 |
| 2026-08-26 | 34 | 49.9 |
| 2026-08-27 | 32.4 | 47.8 |
| 2026-08-28 | 38.6 | 43.1 |
| 2026-08-31 | 41.2 | 41.4 |
| 2026-09-01 | 53.2 | 40.6 |
| 2026-09-02 | 33.8 | 39.8 |
| 2026-09-03 | 37.2 | 39.4 |
| 2026-09-04 | 39.6 | 39.7 |
| 2026-09-08 | 35.5 | 39.2 |
| 2026-09-09 | 65.6 | 40.6 |
| 2026-09-10 | 70 | 42 |
| 2026-09-11 | 50.7 | 42.5 |
| 2026-09-14 | 39.3 | 43.1 |
Le SQL exact derrière chaque chiffre
SELECT
session,
volume_millions,
avg_20d_millions
FROM
(
SELECT
toString(d) AS session,
round(vol / 1e6, 1) AS volume_millions,
round(avg(vol) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) / 1e6, 1) AS avg_20d_millions,
count() OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS sessions_in_window
FROM
(
SELECT
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
AND date >= today() - 75
AND date < today()
GROUP BY date
)
)
WHERE sessions_in_window = 20
ORDER BY sessionLa série quotidienne comporte un point par séance. La série lissée correspond à la moyenne de cette séance et des nineteen précédentes. Le panneau couvre la période allant de 2026-08-17 à 2026-10-02. Elle comprend 34 séances disposant chacune d’une fenêtre complète de twenty sessions. Lors de la dernière de ces séances, AAPL a échangé 33.3 millions de titres, contre une moyenne sur twenty sessions de 42.2 millions. C’est le chiffre affiché par le script le jour de la génération de cette page. Si vous l’exécutez aujourd’hui, les twenty sessions prises en compte auront évolué, et le chiffre aussi.
Changer de ticker
Modifiez TICKER et le script fonctionne avec n’importe quel symbole coté aux États-Unis dans le tableau. La comparaison ci-dessous applique le même calcul sur 20 séances à quatre valeurs très connues. Elle donne rapidement une idée de ce que signifie « volume moyen » entre un ETF indiciel et trois méga-capitalisations.
| ticker | moyenne 20 j (millions) | première séance | dernière séance |
|---|---|---|---|
| NVDA | 110 | 2026-09-04 | 2026-10-02 |
| SPY | 46 | 2026-09-04 | 2026-10-02 |
| AAPL | 42.2 | 2026-09-04 | 2026-10-02 |
| MSFT | 21.3 | 2026-09-04 | 2026-10-02 |
Le SQL exact derrière chaque chiffre
SELECT
ticker,
round(avg(vol) / 1e6, 1) AS avg_20d_millions,
toString(min(d)) AS first_session,
toString(max(d)) AS last_session
FROM
(
SELECT
ticker,
d,
vol,
row_number() OVER (PARTITION BY ticker ORDER BY d DESC) AS rn
FROM
(
SELECT
ticker,
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'MSFT')
AND date >= today() - 45
AND date < today()
GROUP BY ticker, date
)
)
WHERE rn <= 20
GROUP BY ticker
HAVING count() = 20
ORDER BY avg_20d_millions DESCNVDA affiche la moyenne sur 20 séances la plus élevée des quatre, à 110 millions d’actions par séance. MSFT affiche la plus faible, à 21.3 millions. Les calculs portent dans tous les cas sur les séances comprises entre 2026-09-04 et 2026-10-02. Le chiffre d’AAPL correspond ici à celui sur lequel s’arrête la trace ci-dessus : une seule définition, calculée une fois, quel que soit l’endroit où elle apparaît.
À quoi ressemble une réponse 429 et comment réessayer correctement ?
Une limitation de débit se manifeste par un code de statut HTTP, et non par des données. La réponse contient le statut 429 Too Many Requests et souvent un en-tête Retry-After indiquant le nombre de secondes à attendre. Le corps ne contient pas les données demandées. C’est pourquoi l’utilitaire fetch vérifie status_code avant toute analyse. Les règles sont les suivantes, dans l’ordre :
200: analyser et renvoyer le JSON.429ou tout autre5xx: attendreRetry-Afterlorsqu’il est présent. Sinon, appliquer un backoff (2, 4, 8, puis 16 secondes) et réessayer, avec quatre tentatives au total.- Tout autre
4xx: arrêter. Le corps indique précisément pourquoi la requête a été rejetée : table inexistante ou instruction refusée par le contrôle en lecture seule. Réessayer ne changera rien. Par exemple, une table inconnue renvoie un400.
Deux bonnes pratiques permettent d’éviter la limitation. Les résultats préparés sont actualisés au maximum toutes les dix minutes. Une boucle qui interroge le service plus souvent récupère donc des octets identiques, et la mise en cache locale de la réponse ne coûte rien. Demandez uniquement les données nécessaires : LIMIT 20 pour une moyenne sur 20 séances, plutôt qu’une année de lignes à supprimer ensuite. Les défaillances réseau (connexion interrompue, incident DNS) se manifestent par requests.RequestException, que l’utilitaire n’intercepte pas. Entourez l’appel de try/except si le script doit s’exécuter sans surveillance.
FAQ
Existe-t-il une API gratuite de données boursières pour Python ?
Oui. Les endpoints de démonstration de ce guide répondent aux requêtes GET non authentifiées depuis requests, avec du JSON qui se charge directement dans pandas : des requêtes préparées sur /api/demo?q=<key> et du SQL en lecture seule rédigé manuellement sur /api/demo/sql, avec une limite de 500 lignes et un an d’historique. Aucune clé ni inscription requise.
Ai-je besoin d’une clé API pour récupérer des données boursières en Python ?
Pas pour le niveau de démonstration utilisé ici. Une clé gratuite porte la limite à 100 requêtes par jour et donne accès à un historique plus long. Le code Python reste inchangé. Les développeurs qui encapsulent ces mêmes endpoints sous forme d’outils pour un LLM peuvent commencer par compétences en données de marché pour les agents IA.
Comment convertir la réponse JSON d’une API en DataFrame pandas ?
Analysez le corps de la réponse avec resp.json(), puis transmettez la liste d’objets correspondant aux lignes à pd.DataFrame(rows, columns=columns). Convertissez les chaînes de caractères représentant des dates avec pd.to_datetime et celles représentant des valeurs numériques avec pd.to_numeric avant d’effectuer des calculs. Le JSON ne comporte pas de type date et certaines API sérialisent les décimales sous forme de texte.
Que signifie une erreur 429 lors de l’appel d’une API boursière ?
Le code HTTP 429 signifie « Too Many Requests » : le serveur limite le débit de requêtes de l’appelant. Attendez le nombre de secondes indiqué dans l’en-tête Retry-After lorsqu’il est fourni. Sinon, augmentez progressivement le délai entre les tentatives et mettez les réponses en cache au lieu d’interroger à répétition un résultat qui ne se met à jour que toutes les dix minutes.
Comment calculer le volume moyen dans pandas ?
Chargez une ligne par séance avec une colonne volume, conservez les vingt dernières séances complètes, puis appelez df["volume"].mean(). Pour une version glissante sur un historique plus long, utilisez df["volume"].rolling(20).mean(). C’est ce que restitue le panneau de suivi ci-dessus.
Chaque panneau ci-dessus est fourni avec la requête SQL exacte qui le sous-tend, et le script reprend le même principe avec un appel requests en amont. Clé API gratuite. 100 requêtes par jour, 22 ans d’historique, SQL sur les données brutes du tape. Aucune carte bancaire. Obtenir une clé API