brvmdata

FR · EN

Utiliser les données avec un agent

Guide des interfaces publiques de brvmdata : catalogue, CSV, graphiques JSON, observations datées, flux RSS des actualités officielles et citations des sources.

Accès public par GET, sans authentification. Les ressources sont des documents et fichiers : pas d'API de requêtes serveur, de SQL distant ou de mutation. Les observations sont périodiques, pas garanties en temps réel. Aucun conseil en investissement.

Commencer ici

Formats et cohérence d'une édition

Le catalogue versionne son contrat (schema_version), pas les URL des fichiers. data_edition_at identifie l'édition des données ; metadata_generated_at date la description. Chaque jeu donne les colonnes effectivement exportées, le nombre de lignes, les unités connues, la couverture, les rapprochements et les fichiers existants avec taille et SHA-256. Une URL stable peut changer de contenu ; plusieurs GET ne constituent pas une transaction atomique. Vérifier taille et empreinte ; si elles diffèrent, reprendre catalogue et fichier sans cache périmé, sans mélanger les éditions.

CSV : UTF-8, virgule, point décimal, première ligne d'en-tête. storage_type est le type de stockage déclaré, type son affinité ; encoding est CSV text. Lire d'abord les identifiants comme du texte. compression=null signifie CSV non compressé, gzip désigne un fichier .csv.gz à décompresser. L'empreinte porte sur les octets du fichier téléchargé, avant décompression. Unité ou base inconnue : null. primary_key=null : aucune garantie d'unicité générale.

import csv, gzip, hashlib, io, json
from urllib.request import urlopen

BASE = "https://brvmdata.com"
def get_json(path):
    with urlopen(BASE + path, timeout=30) as response:
        return json.load(response)

catalog = get_json("/data/catalog.json")
assert catalog["schema_version"] == 1
datasets = {d["table"]: d for d in catalog["datasets"]}

def rows(name):
    dataset = datasets[name]
    variants = dataset["distributions"]
    download = next((d for d in variants if d["compression"] is None), variants[0])
    with urlopen(download["download_url"], timeout=60) as response:
        raw = response.read()
    if (len(raw) != download["byte_size"] or
            hashlib.sha256(raw).hexdigest() != download["sha256"]):
        raise RuntimeError("Edition changed: refetch catalog and file without stale cache")
    if download["compression"] == "gzip":
        raw = gzip.decompress(raw)
    return list(csv.DictReader(io.StringIO(raw.decode("utf-8"))))

1. Résoudre SNTS puis lire son historique

search.json est une liste : u=chemin canonique français, n=nom, k=type, s=symbole, i=ISIN, x=texte complémentaire ; les clés vides sont absentes. Ne pas confondre symbole d'action et symbole d'obligation. Dans le graphique : s=symbole, d=dates, c=cours, v=volumes ; les tableaux sont alignés par position. prov, lorsqu'il existe, est la première date provisoire. e, lorsqu'il existe, liste les événements officiels de la société dans la période du graphique, du plus ancien au plus récent : [date de publication, type (results, dividend, agm, capital ou listing), titre tel que publié, URL du document original ou null] ; au plus 300, les plus récents. Les cours et volumes des actions sont ajustés des fractionnements et fusionnent BOC, bfin et clôtures provisoires ; ce n'est pas le CSV brut ni une série de rendement total dividendes réinvestis.

search = get_json("/data/search.json")
security = next(item for item in search
                if item.get("s") == "SNTS" and item["k"] in ("action", "radiee"))
print(BASE + security["u"], security["n"], security.get("i"))
chart = get_json("/data/actions/" + security["s"].lower() + ".json")
if chart["d"]:
    print(chart["s"], chart["d"][-1], chart["c"][-1], chart["v"][-1])

Pour les indices, choisir un résultat k=indice et reprendre le slug de u : /data/indices/<slug>.json (exemple ci-dessous). s est le nom de l'indice, c ses niveaux, v est facultatif. Des zéros de v représentent aussi des volumes manquants ou provisoires : ne pas les interpréter automatiquement comme zéro échange. Les anciennes familles et nouvelles bases d'indices ne se raccordent pas sans méthode.

BRVM COMPOSITE JSON

2. Dernier tableau officiel des actions

Filtrer boc_equities sur sa date maximale après avoir retenu les bulletins actifs. Cette date est la dernière séance disponible au bulletin, pas nécessairement le dernier relevé en séance. Conserver les valeurs absentes, le document_id et l'état d'analyse du bulletin ; ne pas recopier un cours précédent comme une nouvelle clôture.

bulletins = {r["document_id"]: r for r in rows("boc_bulletins")
             if r["status"] == "active"}
quotes = [r for r in rows("boc_equities") if r["document_id"] in bulletins]
latest_date = max(r["trading_date"] for r in quotes)
board = [r for r in quotes if r["trading_date"] == latest_date]
print("Official bulletin board:", latest_date)
for quote in board:
    print(quote["symbol"], quote["close_price"] or None,
          quote["volume"] or None, quote["document_id"])

3. Citer le document original

Joindre document_id à id dans le registre <préfixe>_documents de la même source (ici boc_documents). Les IDs ne sont pas globaux. La citation conserve URL du PDF original, séance, date du document, récupération et édition des données ; elle peut aussi renvoyer à la fiche du jeu brvmdata. Vérifier effectivement le document : une URL enregistrée ou HTTP 200 ne prouve pas qu'un PDF complet est disponible.

quote = next(r for r in board if r["symbol"] == "SNTS")
documents = {r["id"]: r for r in rows("boc_documents")}
document = documents[quote["document_id"]]
print({"symbol": quote["symbol"], "session_date": quote["trading_date"],
       "close_price_xof": quote["close_price"] or None,
       "source_url": document["source_url"], "document_date": document["doc_date"],
       "retrieved_at": document["retrieved_at"],
       "data_edition_at": catalog["data_edition_at"],
       "dataset_url": datasets["boc_equities"]["landing_urls"]["en"]})

Échantillon de contrôles des documents AMF

4. Lire une observation de /live.json v2

Le document distingue l'état du marché et la dernière observation de chaque symbole ou indice. equities est indexé par symbole, indices par nom (une entrée peut être null). Leurs observed_at, fetched_at, session_date et basis sont propres à chaque observation. basis=intraday, closing ou unknown. Ne pas dater un cours avec l'horloge globale ni transformer closed en closing ; un relevé de clôture vide peut conserver un cours intrajournalier plus ancien. Les indicateurs de séance, les ratios au bulletin et les historiques conservent leurs dates propres.

live = get_json("/live.json")
assert live["schema_version"] == 2
quote = live.get("equities", {}).get("SNTS")
if quote is not None:
    print({key: quote.get(key) for key in
           ("price_last", "change_pct", "volume", "observed_at", "fetched_at",
            "session_date", "basis", "market_status")})
# live["observed_at"] describes the market-state screen, not every quote.
# basis is intraday, closing or unknown; closed alone does not imply closing.

price_last, price_prev et price_open sont en XOF par action, change_pct en pourcentage, volume en titres ; level est un niveau d'indice. paths contient des couples [HH:MM UTC, cours] observés en séance, uniquement pour la date du relevé global ; ce n'est pas un historique continu ni une clôture fabriquée. L'absence d'un symbole signifie observation indisponible, pas zéro.

5. Suivre les actualités officielles (RSS)

Flux RSS 2.0 : /actualites/rss.xml (les 100 dernières actualités, toutes sources) et /actions/<symbole en minuscules>/rss.xml (les 50 dernières d'une société), en français ; mêmes chemins sous /en/ pour les libellés anglais. Chaque action cotée ou radiée a son flux, éventuellement vide. Un élément donne le titre tel que publié, link = URL du document original à sa source, pubDate = date de publication (minuit UTC quand seule la date est connue), guid stable non permalien (brvmdata:<source>:<identifiant>), category = type, description = type · société (symbole) · source · aussi : autres sources du même événement · via brvmdata. Aucun corps de document n'est repris. lastBuildDate est la publication la plus récente du flux, pas l'heure de génération.

Sources officielles uniquement : publications et annonces des sociétés cotées et avis de la BRVM, bulletin officiel de la cote, AMF-UMOA, directions de la dette, BCEAO. Rattachement aux sociétés par clé explicite de la source ou nom exact imprimé par la BRVM ; un nom ambigu (groupe, pays absent) reste sans société : l'absence de symbole ne prouve pas qu'un document ne concerne aucune société. Un même événement publié par plusieurs sources n'est qu'un élément, au guid du document de la société ; un avis du bulletin publié avant ce document peut donc disparaître au profit d'un nouveau guid. Les avis de paiement de coupons et de remboursement des obligations n'y figurent pas (calendrier des coupons de /obligations/). Interroger au plus quelques fois par heure, avec If-None-Match ou If-Modified-Since.

Règles d'interprétation

  • Dates : séance, observation, publication, effet, détachement, échéance, récupération et génération sont distinctes. Les bornes de couverture n'assurent pas la continuité ; les calendriers peuvent aller dans le futur.
  • Rapprochements : garder identifiants source et dates. Normaliser espaces/casse pour comparer des ISIN, jamais supprimer arbitrairement des caractères ou joindre sur un nom seul. Examiner la cardinalité avant agrégation ; link_issuer_symbols porte méthode et confiance. Pour fund_annual/fund_quarterly, source renvoie à fil_filings.filing_id, puis document_id à fil_documents.id.
  • Valeurs absentes : null et cellule vide ne valent pas zéro. Un zéro explicite ne prouve pas non plus l'absence d'activité si le contrat du graphique indique un remplissage. Ne pas interpoler des séances, rendements ou cours manquants.
  • Unités : _xof est en XOF ; _millions en millions de XOF ; _bn en milliards de XOF. Les comptes sont en millions, le dividende par action en XOF. Une page peut afficher une échelle différente du CSV. Pour com_commodity_monthly.value et les indicateurs macro, lire unit sur chaque ligne ; ne pas supposer USD ou XOF pour toute la table.
  • Dividendes et comptes : distinguer brut, net, fiscalité/IRVM, catégorie d'actionnaire, comptes sociaux/consolidés et base de titres avant/après fractionnement. Un montant annoncé n'est pas automatiquement net. Respecter cumul, exercice, période, source et confiance ; ne pas additionner plusieurs périodes cumulées.
  • Historique à date : published_date_imputed, published_date_evidence, pit_excluded, restated_values et flags signalent limites et révisions. Une date de publication imputée ne démontre pas disponibilité historique ; pit_excluded=true exclut l'observation d'une analyse à date. Les jeux fund sont clos ; nouvelle édition du catalogue ne signifie pas nouveaux comptes.
  • Bulletins et visas : historique officiel incomplet, parse_status peut être partial ; vérifier le PDF. Les séances provisoires ne sont pas des bulletins officiels. amf_issue_visas conserve les versions brutes : montants contradictoires et visas répétés ne sont pas additifs ; la page Émissions rapproche les approbations mais ne transforme pas un visa en levée réalisée.

Requêtes raisonnables et réutilisation

Choisir les fichiers nécessaires dans le catalogue, limiter les requêtes concurrentes, réutiliser une copie locale et respecter Cache-Control. Les réponses de fichiers peuvent fournir ETag et Last-Modified : réutiliser ces validateurs avec If-None-Match ou If-Modified-Since et accepter 304. Ne pas supposer ces en-têtes chez les sources externes ; inspecter leur réponse. Aucun quota garanti ou service de requêtes à haute fréquence n'est annoncé.

L'accès à brvmdata est gratuit ; cela ne constitue pas une licence de redistribution des sources. Un document accessible au public n'est pas automatiquement sous licence ouverte. Les conditions ci-dessous distinguent permissions expresses, restrictions et droits non établis. Citez la source d'origine et brvmdata, sans suggérer leur approbation.

Autorisation non établie ne signifie pas interdiction démontrée. Pour les usages restreints ou incertains, l'éditeur doit établir le fondement applicable : contrat ou autorisation couvrant l'affichage public, les historiques, les fichiers CSV et les données en séance, ou avis juridique sur le périmètre autorisé. Une citation seule ne remplace pas une autorisation exigée. Les droits des tiers et la confidentialité restent applicables.

Aucune garantie : les données sont reproduites et normalisées automatiquement et peuvent contenir des erreurs ; vérifiez toute donnée auprès du document source avant de l'utiliser. Rien ici n'est un conseil en investissement.

Conditions détaillées par source

Métadonnées du catalogue générées le 2026-11-10T22:47:50+00:00 ; édition des données : 2026-11-10T22:45:15+00:00