MetalAPI
XAU3 997,89−1,54 %XAG53,936−1,51 %XPT1 554,47−0,83 %XPD1 047,03−0,44 %XCU6,5080+2,29 %WTI103,27+3,82 %BRENT111,70−1,30 %EUR1,120400−0,01 %GBP1,337500−0,01 %JPY0,0063761+0,14 %CHF1,213445−0,25 %TRY0,020125−0,12 %BTC80 637,11−0,17 %ETH2 354,39−0,06 %
Tous les articles
OrPythonTutoriel

API du cours de l'or en Python : requêtes, cache et graphiques

Utiliser une API du cours de l'or en Python : prix en direct avec requests, erreurs et nouvelles tentatives, cache et série pandas tracée avec matplotlib.

Par MetalAPI Team··7 min de lecture

API du cours de l'or en Python : ce qu'il vous faut

Ce tutoriel montre comment utiliser une API du cours de l'or en Python, de la première requête jusqu'à un client avec cache et un graphique des dix derniers mois. Il utilise MetalAPI, une API REST JSON pour les métaux précieux, les métaux industriels, l'énergie, les devises et les cryptos, avec trois bibliothèques bien connues : requests pour HTTP, pandas pour la série temporelle et matplotlib pour le graphique. Chaque extrait fonctionne avec Python 3.9 ou plus récent.

Créez un compte gratuit, puis créez une clé API sous API keys dans le tableau de bord. Les clés commencent par mk_live_ et ne sont affichées qu'une seule fois : copiez donc la vôtre directement dans une variable d'environnement. Le plan Free ne demande pas de carte bancaire et inclut 100 requêtes par mois avec des prix mis à jour quotidiennement, ce qui suffit pour tout ce que contient cet article.

Installation
pip install requests pandas matplotlib

# macOS et Linux
export METALAPI_KEY="mk_live_your_key_here"

# Windows PowerShell
$env:METALAPI_KEY = "mk_live_your_key_here"

Lisez la clé avec os.environ["METALAPI_KEY"], qui échoue de façon explicite lorsque la variable manque, au lieu de la coller dans un script. Si vous préférez un fichier .env, le paquet python-dotenv le charge en un seul appel à load_dotenv(). Ajoutez .env à .gitignore pour que la clé n'atteigne jamais votre dépôt.

Obtenir le cours de l'or en Python

Chaque endpoint MetalAPI est une requête GET sous https://metalapi.com/api/v1. La clé peut être passée dans un paramètre de requête api_key, un en-tête Authorization: Bearer ou un en-tête X-API-Key. Utilisez un en-tête : les URL complètes finissent dans les journaux, et certaines exceptions de requests affichent l'URL avec sa chaîne de requête. L'endpoint /latest renvoie les prix actuels, base définit la devise et currencies limite la réponse aux symboles dont vous avez besoin :

gold.py
import os

import requests

resp = requests.get(
    "https://metalapi.com/api/v1/latest",
    params={"base": "USD", "currencies": "XAU"},
    headers={"X-API-Key": os.environ["METALAPI_KEY"]},
    timeout=10,
)
data = resp.json()
if not data["success"]:
    raise SystemExit(f"MetalAPI error {data['error']['code']}: {data['error']['info']}")

price = data["rates"][data["base"] + "XAU"]
print(f"Gold: {price:,.2f} {data['base']} per troy ounce")

La réponse ressemble à ceci. Les chiffres sont les valeurs d'exemple utilisées dans toute la documentation MetalAPI, pas des prix en direct :

Exemple de réponse
{
  "success": true,
  "timestamp": 1792834892,
  "date": "2026-10-24",
  "base": "USD",
  "rates": { "XAU": 0.0002501319, "USDXAU": 3997.89 },
  "rate_limit": { "limit": 100, "remaining": 99, "used": 1, "plan": "Free", "...": "..." }
}

Avec ces valeurs, le script affiche Gold: 3,997.89 USD per troy ounce. rates["USDXAU"] est le prix d'une once troy d'or dans la devise de base. rates["XAU"] en est l'inverse, soit des onces d'or par dollar, et le lire par erreur est le bug le plus fréquent dans le code qui traite le cours de l'or. Construire la clé à partir de data["base"] garde le script correct lorsque vous passez à base=EUR (clé EURXAU) ou à base=INR. Ajoutez unit=gram ou unit=kilogram pour obtenir des prix au gramme ou au kilogramme. timestamp est l'heure des données de prix en secondes Unix, pas celle de votre requête.

Gérer les erreurs de l'API du cours de l'or en Python

Un appel en échec renvoie un statut d'erreur HTTP et un corps JSON avec success: false et un objet error qui contient un code numérique, un type et un message info. Basez votre logique sur le code, jamais sur le texte du message. Seuls quelques codes méritent une nouvelle tentative :

  • 104 (too_many_requests, HTTP 429) : la limite par minute. Attendez le nombre de secondes indiqué dans l'en-tête Retry-After.
  • 500, 503 et erreurs réseau : réessayez avec un backoff exponentiel.
  • Tout le reste, comme 101 ou 102 (clé absente ou invalide), 105 (quota mensuel épuisé), 106 (endpoint non inclus dans votre plan) et les erreurs de validation 2xx : corrigez plutôt la requête, la clé ou le plan.

Ce petit module encapsule cette logique. Il réutilise une seule requests.Session, pour que les connexions restent ouvertes entre les appels, et lève une exception que vous pouvez inspecter :

metalapi.py
import os
import time

import requests

API_URL = "https://metalapi.com/api/v1"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["METALAPI_KEY"]


class MetalAPIError(Exception):
    def __init__(self, code, type_, info, retry_after=None):
        super().__init__(f"MetalAPI {code} ({type_}): {info}")
        self.code = code
        self.type = type_
        self.retry_after = retry_after


def _get(path, params):
    r = session.get(f"{API_URL}/{path}", params=params, timeout=10)
    try:
        data = r.json()
    except ValueError:  # pas du JSON, p. ex. une page d'erreur HTML renvoyée par un proxy
        raise MetalAPIError(r.status_code, "http_error", r.reason) from None
    if not data.get("success"):
        err = data["error"]
        retry_after = r.headers.get("Retry-After")
        raise MetalAPIError(err["code"], err["type"], err["info"], int(retry_after) if retry_after else None)
    return data


def call(path, params=None, retries=3):
    """Appelle un endpoint en GET. Ne réessaie qu'après une limite de débit, une erreur serveur ou une panne réseau."""
    for attempt in range(retries + 1):
        try:
            return _get(path, params or {})
        except (MetalAPIError, requests.RequestException) as exc:
            code = getattr(exc, "code", None)  # None pour les erreurs réseau
            if attempt == retries or (code is not None and code != 104 and code < 500):
                raise
            time.sleep(getattr(exc, "retry_after", None) or 2**attempt)

Le backoff attend 1, 2 puis 4 secondes avant d'abandonner. Les nouvelles tentatives ne coûtent rien ici, car les requêtes rejetées avec 104 ou une erreur serveur ne sont pas facturées. Les erreurs de validation le sont, ce qui est une raison de plus de ne jamais les réessayer en boucle. Le guide de gestion des erreurs liste chaque code avec son statut HTTP.

Mettre en cache le cours de l'or pour économiser des requêtes

Chaque plan rafraîchit les prix à intervalle fixe : une fois par jour sur Free, toutes les 30 minutes sur Lite, toutes les 10 minutes sur Standard et toutes les 60 secondes sur Advanced et Pro. Les mises à jour tombent sur des multiples de cet intervalle comptés à partir de 00:00 UTC : redemander avant la suivante renvoie les mêmes chiffres et consomme du quota pour rien. Ce cache conserve chaque réponse jusqu'à la mise à jour suivante et sert les derniers prix valides si l'API est injoignable :

prices.py
import math
import time

import requests

from metalapi import MetalAPIError, call

# Secondes. Free : 86_400, Lite : 3_600 (une fois par heure tient dans ses 1000 requêtes), Standard : 600
UPDATE_INTERVAL = 600
_cache = {}  # (base, symbols, unit) -> (expires_at, data)


def latest(base="USD", symbols="XAU,XAG", unit="troy_oz"):
    key = (base, symbols, unit)
    now = time.time()
    hit = _cache.get(key)
    if hit and now < hit[0]:
        return hit[1]
    try:
        data = call("latest", {"base": base, "currencies": symbols, "unit": unit})
    except (MetalAPIError, requests.RequestException):
        if not hit:
            raise
        _cache[key] = (now + 60, hit[1])  # sert les derniers prix valides, nouvel essai dans une minute
        return hit[1]
    next_update = (math.floor(now / UPDATE_INTERVAL) + 1) * UPDATE_INTERVAL
    _cache[key] = (next_update + 5, data)  # expire quelques secondes après la mise à jour suivante
    return data

latest("EUR", "XAU", "gram")["rates"]["EURXAU"] renvoie le prix en euros par gramme. Demandez tous les symboles nécessaires en un seul appel : XAU,XAG,XPT,XPD coûte une requête, pas quatre. Gardez à l'esprit que ce cache vit en mémoire. Un script lancé par cron démarre chaque fois avec un cache vide : dans ce cas, c'est la planification qui sert de cache, et le script ne doit pas tourner plus d'une fois par intervalle de mise à jour. Pour surveiller votre quota, appelez /usage, qui n'est jamais décompté.

Charger une série temporelle du cours de l'or dans pandas

Pour l'historique, l'endpoint /timeframe (alias /timeseries) renvoie les cours de clôture quotidiens entre start_date et end_date, indexés par date. Une requête couvre jusqu'à 30 jours sur le plan Free et 365 jours sur les plans payants ; une période plus longue échoue avec l'erreur 207. Cette fonction découpe n'importe quelle période en tranches autorisées et renvoie une Series pandas :

history.py
from datetime import date, timedelta

import pandas as pd

from metalapi import call


def gold_history(start, end, base="USD", max_days=30):  # max_days=365 sur les plans payants
    rates = {}
    chunk_start = start
    while chunk_start <= end:
        chunk_end = min(chunk_start + timedelta(days=max_days - 1), end)
        data = call("timeframe", {
            "start_date": chunk_start.isoformat(),
            "end_date": chunk_end.isoformat(),
            "currencies": "XAU",
            "base": base,
        })
        rates.update(data["rates"])
        chunk_start = chunk_end + timedelta(days=1)

    df = pd.DataFrame.from_dict(rates, orient="index")
    df.index = pd.to_datetime(df.index)
    return df[f"{base}XAU"].sort_index().rename(f"gold_{base.lower()}")


gold = gold_history(date(2026, 1, 1), date(2026, 10, 23))
gold.to_csv("gold_usd.csv")
print(f"{len(gold)} trading days, high {gold.max():,.2f}, low {gold.min():,.2f}")

Sur le plan Free, ces dix mois demandent 10 requêtes. La Series contient une ligne par jour de cotation : les week-ends et les jours fériés des marchés sont absents au lieu d'être remplis, attendez-vous donc à moins de lignes que de jours calendaires. Si vous avez besoin d'une valeur pour chaque jour, gold.asfreq("D").ffill() reporte la clôture du vendredi sur le week-end. À partir de là, les outils pandas habituels s'appliquent, comme gold.pct_change() pour les rendements quotidiens ou gold.rolling(20).mean() pour une moyenne mobile.

Les cours de clôture passés ne changent jamais : le fichier CSV sert donc de cache pour l'historique. Chargez-le la fois suivante au lieu de redemander la même période. Pour charger tout l'historique depuis 2000 dans une base de données, consultez Cours historiques de l'or et de l'argent par API.

Tracer le cours de l'or avec matplotlib

Le graphique lit le fichier CSV : le redessiner ne coûte donc aucune requête. Il trace la clôture quotidienne avec une moyenne mobile sur 20 jours :

plot.py
import matplotlib.pyplot as plt
import pandas as pd

gold = pd.read_csv("gold_usd.csv", index_col=0, parse_dates=True)["gold_usd"]
sma20 = gold.rolling(20).mean()

fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(gold.index, gold, linewidth=1, label="Gold, USD per troy oz")
ax.plot(sma20.index, sma20, linewidth=2, label="20-day moving average")
ax.set_title(f"Gold price, {gold.index[0]:%d %b %Y} to {gold.index[-1]:%d %b %Y}")
ax.yaxis.set_major_formatter("{x:,.0f}")
ax.grid(alpha=0.3)
ax.legend()
fig.autofmt_xdate()
fig.tight_layout()
fig.savefig("gold.png", dpi=150)

Comme l'index contient de vraies dates, matplotlib espace les points selon le temps, et la moyenne mobile commence au 20e jour de cotation. Pour représenter l'or dans une autre devise, appelez gold_history avec base="EUR" : les clés de taux suivent le même schéma, la fonction fonctionne donc sans modification.

Étapes suivantes

Vous disposez maintenant de ce dont la plupart des intégrations Python ont besoin : une requête authentifiée, une gestion des erreurs avec des nouvelles tentatives raisonnables, un cache qui suit l'intervalle de mise à jour de votre plan et l'historique dans pandas. Construisez et testez le tout sur le plan Free. Lorsque vous avez besoin de prix plus frais, choisissez un plan selon l'intervalle de mise à jour et le volume : une tâche exécutée toutes les 10 minutes consomme environ 4 500 requêtes par mois, ce qui tient dans les 10 000 du plan Standard. Comparez les plans sur la page des tarifs.

Si les prix doivent s'afficher sur un site web, API du cours de l'or en JavaScript montre comment les servir depuis une route Node.js sans exposer votre clé.

Questions fréquentes

Existe-t-il une API gratuite du cours de l'or pour Python ?

Oui. Le plan Free de MetalAPI inclut 100 requêtes par mois avec des prix mis à jour quotidiennement, sans carte bancaire, et chaque extrait de cet article fonctionne avec lui. Ses derniers prix correspondent à la clôture de la veille, et une requête /timeframe couvre jusqu'à 30 jours. API gratuite du cours de l'or présente l'offre gratuite plus en détail.

Pourquoi le taux XAU est-il un nombre si petit ?

rates["XAU"] est le taux inverse : des onces troy d'or par unité de la devise de base. Le prix se trouve sous la clé préfixée, rates["USDXAU"] pour la base USD ou rates["EURXAU"] pour la base EUR. Avec les valeurs d'exemple, 1 ÷ 0,0002501319 redonne 3 997,89.

Comment obtenir le prix de l'or au gramme en Python ?

Ajoutez unit=gram à la requête, par exemple call("latest", {"base": "EUR", "currencies": "XAU", "unit": "gram"}). rates["EURXAU"] est alors le prix en euros par gramme, et la réponse inclut "unit": "gram". Évitez unit=ounce : il s'agit de l'once avoirdupois, et non de l'once troy dans laquelle l'or est coté.

Jusqu'où remonte l'historique du cours de l'or ?

Les cours quotidiens de l'or commencent en 2000, et chaque plan peut interroger tout l'historique. Les plans ne diffèrent que par le nombre de jours qu'une requête /timeframe peut couvrir : 30 sur Free et 365 sur les plans payants. Avec max_days=365, tout l'historique de 2000 à aujourd'hui demande 27 requêtes.

À quelle fréquence un script Python doit-il appeler l'API ?

Pas plus souvent que votre plan ne met à jour les prix : une fois par jour sur Free, après 00:00 UTC ; toutes les heures sur Lite, car un appel toutes les 30 minutes demanderait environ 1 490 requêtes par mois pour un quota de 1 000 ; toutes les 10 minutes sur Standard. Attendez-vous à des prix figés le week-end, car l'or au comptant ne se négocie pas du vendredi 17:00 au dimanche 17:00, heure de New York.

Essayez avec vos propres données

Créez un compte gratuit et envoyez votre première requête en moins de deux minutes. 100 requêtes par mois, sans carte bancaire.

À lire aussi