Note de la rédaction : ce panorama s'appuie principalement sur des sources et services des marchés russophones (CEI) ; les principes décrits restent applicables aux marchés francophones et européens.
Cet article est le pendant de « Parsing des taux de change ». Beaucoup s'y transpose presque sans changement — en particulier la discipline des types de données (aucun float pour les prix) et les pratiques de cache. Mais les cours d'actions ont leur spécificité : tickers et places boursières, séances de cotation et jours fériés, opérations sur titres (splits et dividendes), et — différence majeure avec les taux des banques centrales — la licence des données. L'information boursière en temps réel est régie par les bourses et les régulateurs et, contrairement au taux officiel d'une banque centrale librement publié, elle est loin de pouvoir toujours être obtenue gratuitement et, plus encore, redistribuée.
1. Qu'est-ce qu'un « cours » et quelles données existent
Selon la tâche, on entend par « cours » des choses différentes, et c'est la première à préciser :
- Last / prix courant — le prix de la dernière transaction. Ce qu'affiche le widget « prix de l'action maintenant ».
- Barre OHLCV (bougie) — Open, High, Low, Close et Volume sur un intervalle (minute, heure, jour). La base des graphiques et des backtests.
- Bid/Ask (carnet d'ordres) — les meilleurs prix d'achat et de vente. Nécessaires au trading, ce sont généralement les données les plus « chères » et les plus licenciées.
- EOD (end-of-day) — les prix de clôture du jour. Bon marché/gratuit, adapté à l'analytique et au suivi de portefeuille.
- Adjusted close — le prix de clôture corrigé des splits et dividendes, pour que l'historique soit continu.
Et l'arbitrage clé sur la « fraîcheur » : temps réel → délai de 15–20 minutes → end-of-day. Plus les données sont fraîches et détaillées, plus la licence est stricte et le prix élevé.
2. Bourses de la CEI et sources régionales
La principale source « accueillante pour le développeur » de la région, c'est l'API MOEX ISS. Un service REST complet de la Bourse de Moscou, qui fournit actions, obligations, contrats à terme, indices et paires de devises en JSON/XML/CSV. Les données avec un délai d'environ 15 minutes sont gratuites ; le temps réel et le carnet d'ordres — sur abonnement.
| Bourse | Ce qu'elle fournit | Accès |
|---|---|---|
MOEX (Moscou), iss.moex.com |
actions, obligations, contrats à terme, indices, paires de devises | délai ~15 min gratuit ; temps réel et carnet — sur abonnement |
KASE (Kazakhstan), kase.kz |
actions, indice KASE, obligations | données publiées sur le site ; pas d'API REST publique stable de niveau MOEX en général — via des fournisseurs ou par parsing |
UZSE (Tachkent), uzse.uz |
actions des émetteurs ouzbeks | données sur le site |
| Bourse SPB | titres étrangers | site/fournisseurs |
Pont commode vers l'article sur les devises : MOEX ISS fournit non seulement des actions, mais aussi des paires de devises (par exemple
USD000UTSTOM— le dollar contre le rouble en bourse). Autrement dit, une seule intégration à ISS couvre à la fois les actions et le taux de change boursier — boursier, et non le taux officiel de la banque centrale.
Particularité du format ISS : les données arrivent « en colonnes » — d'un côté un tableau des noms de colonnes (columns), de l'autre un tableau des lignes (data). Pour extraire le champ voulu, il faut faire correspondre le nom de la colonne à son indice, sans se fier à un ordre fixe (voir l'exemple en PHP).
3. Les API mondiales
Ici, pas de source libre « comme la banque centrale » : toutes exigent une clé et des limites, et le temps réel est presque toujours payant.
| Service | Couverture | Clé | Limite gratuite | Notes |
|---|---|---|---|---|
| MOEX ISS | marchés MOEX | inutile | délai ~15 min | temps réel payant |
| Finnhub | actions US et internat., FX, crypto | requise | ~60 requêtes/min | WebSocket dispo ; historique limité en gratuit |
| Twelve Data | actions, FX, crypto | requise | ~800 requêtes/jour | OHLC, indicateurs, REST pur |
| Alpha Vantage | 200k+ tickers, 20+ bourses | requise | 25 requêtes/jour (5/min) | EOD et indicateurs ; temps réel US — payant |
| yfinance (Yahoo non off.) | très large | inutile | pas de limite explicite, mais c'est du scraping | pour prototypes et apprentissage, pas pour la production |
| EODHD | 150k+ tickers mondiaux | requise | essai | fort en export massif d'historique |
| Financial Modeling Prep | prix + fondamentaux | requise | limité | comptes, multiples |
| Tiingo | EOD + fondamentaux US | requise | limité | end-of-day propres |
| Marketstack / Polygon.io | mondial / temps réel US | requise | essai / de fait payant | Polygon — faible latence, ticks |
Détail récent important : IEX Cloud a fermé le 31 août 2024. Si un guide le recommande encore, c'est une information périmée ; les remplaçants les plus proches sont Alpha Vantage et Financial Modeling Prep.
Les forfaits gratuits conviennent parfaitement au prototype, mais butent vite sur les limites (25 requêtes par jour chez Alpha Vantage, c'est littéralement quelques dizaines de tickers par jour). Aussi, en forfait gratuit, on construit toujours la logique autour du cache et du stockage local : on tire l'historique une fois, ensuite on ne met à jour que les points frais.
4. En quoi les cours d'actions sont plus complexes que les taux de change
Quelques différences qui cassent un parseur naïf :
- Le ticker n'est pas unique. Un même symbole peut se négocier sur différentes bourses. La clé d'un instrument est donc le couple « bourse + ticker » ; plus fiable encore, les identifiants internationaux ISIN ou FIGI.
- Séances de cotation et jours fériés. Chaque bourse a son horaire, son fuseau, ses séances pré- et post-marché. La « dernière cotation » le week-end, c'est le prix de vendredi.
- Opérations sur titres. Un split 1:10 « fait tomber » le prix d'un facteur 10 — mais ce n'est pas une chute du marché, c'est un recalcul technique. Analogue du champ
valeur nominalede l'article sur les devises : l'ignorer donne une fausse anomalie. Dividendes et splits se prennent en compte via l'adjusted close. - Devise de l'instrument. Le prix d'un titre s'exprime dans la devise de la bourse (RUB, USD, KZT…), et pour un portefeuille dans une seule devise, il faut le convertir — c'est là que servent les taux de l'article voisin.
- Volume. C'est un entier, mais très grand — des millions et des milliards d'unités. Il lui faut un type entier 64 bits.
5. Cinq solutions dans différents langages
Chaque exemple frappe une source différente, pour montrer des formats variés. Partout, l'accent est mis sur deux points de l'article sur les devises : le prix se stocke en type décimal (pas float), le volume — en type entier.
5.1. PHP — MOEX ISS (JSON en colonnes, CEI)
<?php
declare(strict_types=1);
/**
* Dernier prix d'une action à la Bourse de Moscou, régime principal TQBR.
* Gratuit — avec un délai d'environ 15 minutes.
*/
function moexLastPrice(string $ticker, string $board = 'TQBR'): ?string
{
$url = sprintf(
'https://iss.moex.com/iss/engines/stock/markets/shares/boards/%s/securities/%s.json?iss.meta=off',
$board,
$ticker
);
$raw = file_get_contents($url);
if ($raw === false) {
throw new RuntimeException('Impossible de récupérer les données MOEX');
}
$json = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// ISS fournit les données « en colonnes » : columns d'un côté, data de l'autre
$columns = $json['marketdata']['columns'] ?? [];
$rows = $json['marketdata']['data'] ?? [];
if (!$rows) {
return null; // pas de cotation ou ticker invalide
}
$idx = array_flip($columns); // nom de colonne -> indice
$last = $rows[0][$idx['LAST']] ?? null; // prix de la dernière transaction
return $last === null ? null : (string) $last;
}
echo 'SBER: ' . (moexLastPrice('SBER') ?? 'aucune donnée') . " RUB\n";
echo 'GAZP: ' . (moexLastPrice('GAZP') ?? 'aucune donnée') . " RUB\n";
Ici, le array_flip($columns) est révélateur — la mise en correspondance du nom de colonne avec son indice. C'est la bonne façon de lire ISS : l'ordre des champs n'est pas garanti, et les indices « magiques » du genre [12] cassent au moindre changement de la réponse. Pour un prix exact au bit près, mieux vaut demander du CSV ou stocker le texte brut — le décodeur JSON transforme déjà le nombre en float.
5.2. Python — Finnhub (cotation courante, Decimal)
import os
import requests
from decimal import Decimal
FINNHUB_TOKEN = os.environ["FINNHUB_TOKEN"] # clé gratuite, ~60 requêtes/min
def finnhub_quote(symbol: str) -> dict[str, Decimal]:
"""Cotation courante d'un symbole (en forfait gratuit — marché US)."""
resp = requests.get(
"https://finnhub.io/api/v1/quote",
params={"symbol": symbol, "token": FINNHUB_TOKEN},
timeout=10,
)
resp.raise_for_status()
d = resp.json()
# Decimal(str(...)) fige exactement la valeur reçue dans le JSON
return {
"current": Decimal(str(d["c"])), # prix courant
"open": Decimal(str(d["o"])),
"high": Decimal(str(d["h"])),
"low": Decimal(str(d["l"])),
"prev_close": Decimal(str(d["pc"])),
}
if __name__ == "__main__":
q = finnhub_quote("AAPL")
print(f"AAPL : {q['current']} USD (ouv. {q['open']}, max. {q['high']})")
5.3. JavaScript / Node.js — Twelve Data (OHLC, volume)
// Gratuit : ~800 requêtes par jour. Clé obligatoire.
const API_KEY = process.env.TWELVE_DATA_KEY;
async function twelveQuote(symbol) {
const url = new URL("https://api.twelvedata.com/quote");
url.searchParams.set("symbol", symbol);
url.searchParams.set("apikey", API_KEY);
const res = await fetch(url);
const d = await res.json();
if (d.status === "error") throw new Error(d.message);
return {
symbol: d.symbol,
open: d.open, // des chaînes — on ne les convertit pas en Number sans nécessité
high: d.high,
low: d.low,
close: d.close,
volume: d.volume, // volume — grand entier, on le garde en chaîne/BigInt
exchange: d.exchange,
};
}
twelveQuote("MSFT").then((q) =>
console.log(`${q.symbol} (${q.exchange}): close ${q.close}, vol ${q.volume}`)
);
JavaScript n'a pas de type décimal, et Number est un double. C'est pourquoi les prix restent des chaînes ; pour l'arithmétique sur les prix, on prend decimal.js/big.js, et pour le volume — le BigInt natif.
5.4. Go — Alpha Vantage (bougies journalières OHLCV)
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sort"
"time"
)
// Alpha Vantage : gratuit 25 requêtes/jour, 5/min.
type avDaily struct {
Series map[string]struct {
Open string `json:"1. open"`
High string `json:"2. high"`
Low string `json:"3. low"`
Close string `json:"4. close"`
Volume string `json:"5. volume"`
} `json:"Time Series (Daily)"`
}
func dailyBars(symbol string) (avDaily, error) {
key := os.Getenv("ALPHAVANTAGE_KEY")
url := fmt.Sprintf(
"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=%s&apikey=%s",
symbol, key,
)
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Get(url)
if err != nil {
return avDaily{}, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var out avDaily
if err := json.Unmarshal(body, &out); err != nil {
return avDaily{}, err
}
return out, nil
}
func main() {
bars, err := dailyBars("IBM")
if err != nil {
panic(err)
}
// on trouve la bougie la plus récente par date
dates := make([]string, 0, len(bars.Series))
for d := range bars.Series {
dates = append(dates, d)
}
sort.Strings(dates)
last := dates[len(dates)-1]
b := bars.Series[last]
// les prix restent des chaînes ; pour les calculs — shopspring/decimal
fmt.Printf("IBM %s: O=%s H=%s L=%s C=%s V=%s\n",
last, b.Open, b.High, b.Low, b.Close, b.Volume)
}
Avantage pratique : Alpha Vantage fournit les prix sous forme de chaînes — la précision décimale est préservée d'origine, il suffit de ne pas les transformer en float64.
5.5. C# / .NET — Yahoo Finance (endpoint non officiel, decimal)
using System.Text.Json;
// ⚠️ Endpoint non officiel de Yahoo Finance — le même qu'utilise la bibliothèque yfinance.
// Bon pour prototypes et apprentissage, mais sans garanties et PAS pour la production.
public static class YahooChart
{
private static readonly HttpClient Http = new();
public static async Task<(string Date, decimal Close)> LastDailyCloseAsync(string symbol)
{
var url = $"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=5d";
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.UserAgent.ParseAdd("Mozilla/5.0"); // Yahoo exige un User-Agent
var resp = await Http.SendAsync(req);
resp.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStreamAsync());
var result = doc.RootElement.GetProperty("chart").GetProperty("result")[0];
var timestamps = result.GetProperty("timestamp");
var closes = result.GetProperty("indicators")
.GetProperty("quote")[0].GetProperty("close");
var i = timestamps.GetArrayLength() - 1;
var unix = timestamps[i].GetInt64();
var close = closes[i].GetDecimal(); // decimal — correct pour un prix
var date = DateTimeOffset.FromUnixTimeSeconds(unix).ToString("yyyy-MM-dd");
return (date, close);
}
}
class Program
{
static async Task Main()
{
var (date, close) = await YahooChart.LastDailyCloseAsync("AAPL");
Console.WriteLine($"AAPL close {date}: {close} USD");
}
}
Cet exemple montre honnêtement la voie « scraping » via Yahoo : pas de clé, des données proches du temps réel, mais un endpoint non officiel qui peut changer sans préavis — impossible d'y bâtir de la production.
6. Dans quel type de données stocker les cours
La règle de base est exactement celle de l'article sur les taux de change : les prix et les montants monétaires — uniquement en type décimal, jamais en float/double. La virgule flottante binaire accumule des erreurs d'arrondi, et sur des backtests couvrant une longue période, cela produit des écarts impossibles à réconcilier.
| Champ | Type | Pourquoi |
|---|---|---|
| Prix (open/high/low/close, last) | NUMERIC(18,6) / Decimal / decimal |
précision sans perte |
| Volume | BIGINT / int64 |
des milliards d'unités ne tiennent pas dans un int ordinaire |
| Adjusted close | NUMERIC(18,6) |
à stocker à côté du prix « brut », pas à la place |
| Horodatage de la barre | TIMESTAMPTZ |
obligatoirement avec le fuseau de la bourse |
| Devise de l'instrument | CHAR(3) (ISO 4217) |
pour la conversion du portefeuille |
Exemple de table pour les bougies (OHLCV) :
CREATE TABLE quotes (
id BIGSERIAL PRIMARY KEY,
source VARCHAR(16) NOT NULL, -- 'MOEX', 'FINNHUB', 'AV', 'YAHOO'
exchange VARCHAR(16) NOT NULL, -- bourse : 'MOEX', 'NASDAQ'...
ticker VARCHAR(20) NOT NULL, -- SBER, AAPL...
isin CHAR(12), -- identifiant fiable de l'instrument
ccy CHAR(3) NOT NULL, -- devise du prix : RUB, USD...
ts TIMESTAMPTZ NOT NULL, -- horodatage de la barre/cotation
interval VARCHAR(8) NOT NULL DEFAULT '1d', -- 1m | 1h | 1d
open NUMERIC(18,6) NOT NULL,
high NUMERIC(18,6) NOT NULL,
low NUMERIC(18,6) NOT NULL,
close NUMERIC(18,6) NOT NULL,
adj_close NUMERIC(18,6), -- corrigé des splits/dividendes
volume BIGINT NOT NULL DEFAULT 0,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (source, exchange, ticker, interval, ts)
);
Il faut stocker à part les opérations sur titres (splits avec leur coefficient et dividendes avec leur date), car à leur survenue il faut recalculer rétroactivement l'historique de l'adj_close.
7. Conseils pratiques
- Distinguez temps réel, délai et EOD. Pour la plupart des tâches (portefeuille, analytique, tableau de bord), les données avec délai ou end-of-day suffisent — elles sont moins chères et plus simples à licencier.
- Respectez les licences. C'est la différence majeure avec les taux d'une banque centrale. Les données boursières en temps réel sont régulées (bourses, FINRA, SEC), et même leur simple réaffichage aux utilisateurs peut exiger un accord. C'est pourquoi, par exemple, le temps réel US chez Alpha Vantage est payant. Avant de publier des données, vérifiez les conditions de la source.
- Construisez tout autour du cache. Avec une limite de 25 requêtes par jour, pas d'autre choix : vous tirez l'historique une fois, ensuite seulement des mises à jour incrémentales, le reste vient de votre base.
- Traitez les splits comme la « valeur nominale ». Un saut brusque du prix d'un facteur N est presque toujours une opération sur titre, pas un mouvement de marché. Une simple vérification « variation par rapport à la veille supérieure à X % » attrape aussi bien les splits que les erreurs de parsing.
- Identifiez l'instrument par bourse + ticker (mieux, par ISIN/FIGI). Un même ticker vit sur plusieurs places.
- Pensez aux séances et aux jours fériés. Une réponse vide un jour férié est normale ; prenez la dernière date disponible de la réponse, pas celle demandée.
- Ne bâtissez pas de production sur le scraping de Yahoo. Pour un prototype — parfait ; pour un service censé vivre des années — prenez une API avec garanties.
8. Où cela s'applique
- Trackers de portefeuille — valeur courante des actifs, P&L, conversion en devise de référence.
- Trading algorithmique et bots de trading — signaux et exécution (ici, temps réel et carnet d'ordres deviennent nécessaires).
- Tableaux de bord et BI — visualisation du marché, vues sectorielles.
- Screeners et backtest — filtrage des titres et test des stratégies sur l'historique (OHLCV + adjusted).
- Robo-advisors et fintech — recommandations et gestion automatique.
- Comptabilité et valorisation — réévaluation des placements au prix de marché à une date.
- Alertes — notifications lorsque le prix atteint un niveau donné.
En résumé
La collecte des cours boursiers est techniquement proche de celle des taux de change — mêmes types décimaux, même cache, même prudence face aux sauts « techniques » (à la place de la valeur nominale d'une devise, ici les splits). Mais trois choses s'ajoutent : le ticker est lié à une bourse (et mieux vaut l'ISIN/FIGI), l'historique exige une correction des opérations sur titres, et — le plus important en pratique — les données sont licenciées : presque partout, seuls le délai et l'end-of-day sont gratuits, tandis que le temps réel coûte de l'argent et vient avec des conditions. Dans la région, la source publique la plus commode est MOEX ISS, qui fournit aussi les paires de devises boursières, rejoignant le thème de l'article voisin sur les taux de change. Additionnez la discipline des types, le cache et le respect des licences — et vous obtiendrez des cours sur lesquels appuyer vos calculs.