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 329,75−0,55 %ETH2 329,36−1,13 %
Tous les articles
OrArgentDonnées historiquesTutoriel

Cours historiques de l'or et de l'argent par API : 25 ans de données quotidiennes

Obtenez les cours quotidiens de l'or et de l'argent depuis 2000 avec /historical, /timeframe, /change et /ohlc, paginez par année, stockez et tracez vos séries.

Par MetalAPI Team··10 min de lecture

Quel historique est disponible

MetalAPI conserve une barre quotidienne par symbole et par jour, avec ouverture, plus haut, plus bas et clôture. Pour l'or et l'argent, cela représente plus de 25 ans de données quotidiennes : de quoi tracer des tendances de long terme, tester une règle de prix sur le passé ou réévaluer d'anciennes factures au cours du jour.

CatégorieSymbolesHistorique quotidien depuis
Métaux précieuxXAU, XAG, XPT, XPD2000
Métaux industriels et énergieXCU, ALU, ZNC, HRC, WTI, BRENT et autres2000
Devises57, dont USD, EUR, GBP et JPY4 janvier 1999
Cryptomonnaies13, dont BTC et ETH2014

Tous les plans peuvent interroger l'historique complet. Les plans ne diffèrent que par le nombre de jours qu'une seule requête de période peut couvrir. Les dates utilisent le format YYYY-MM-DD en UTC, et les valeurs de chaque jour sont les cours de clôture de ce jour. Une date antérieure au début de l'historique d'un symbole renvoie l'erreur 300 (no_results).

Les cours des métaux antérieurs à octobre 2026 proviennent des contrats à terme de première échéance : ils peuvent donc différer légèrement du prix spot du même jour. Gardez-le en tête lorsque vous comparez d'anciennes valeurs avec des prix spot issus d'une autre source.

Quatre endpoints couvrent le travail sur l'historique : /historical pour un jour, /timeframe pour une période, /change pour la variation entre deux jours et /ohlc pour la bougie d'un jour. Les sections suivantes les passent en revue, puis construisent une petite archive locale surmontée d'un graphique.

Un jour : /historical

Pour obtenir les cours de clôture d'un seul jour, placez la date dans le chemin. /historical?date=YYYY-MM-DD fait la même chose, et /yesterday est un raccourci pour le jour UTC précédent :

bash
curl "https://metalapi.com/api/v1/2024-01-15?currencies=XAU,XAG" \
  -H "X-API-Key: $METALAPI_KEY"

La réponse a la même forme que celle de /latest, avec en plus historical: true. Voici les valeurs d'exemple de la référence de l'API :

Exemple de réponse
{
  "success": true,
  "timestamp": 1705276800,
  "historical": true,
  "date": "2024-01-15",
  "base": "USD",
  "rates": {
    "XAU": 0.0004874244,
    "USDXAU": 2051.6,
    "XAG": 0.0431741646,
    "USDXAG": 23.162
  }
}

USDXAU est le prix d'une once troy d'or dans la devise de base, et XAU en est l'inverse. base et unit fonctionnent comme d'habitude : base=EUR&unit=gram renvoie donc des euros par gramme pour ce jour.

Les week-ends et jours fériés ne font pas échouer la requête : pour un jour sans cotation, c'est la clôture du dernier jour de cotation qui est renvoyée. Le champ date de la réponse reste le jour demandé : si vous devez savoir si les marchés étaient ouverts, tenez un calendrier de cotation de votre côté. Une date future renvoie l'erreur 205 (invalid_date).

Périodes : /timeframe

Pour une série, /timeframe renvoie une entrée par jour entre start_date et end_date, toutes deux incluses, indexée par date. /timeseries est un alias qui marque la réponse avec timeseries: true au lieu de timeframe: true.

bash
curl "https://metalapi.com/api/v1/timeframe?start_date=2026-10-19&end_date=2026-10-23&currencies=XAU" \
  -H "X-API-Key: $METALAPI_KEY"
Exemple de réponse
{
  "success": true,
  "timeframe": true,
  "start_date": "2026-10-19",
  "end_date": "2026-10-23",
  "base": "USD",
  "rates": {
    "2026-10-19": { "XAU": 0.0002403788, "USDXAU": 4160.1 },
    "2026-10-20": { "XAU": 0.0002413943, "USDXAU": 4142.6 },
    "2026-10-21": { "XAU": 0.0002409639, "USDXAU": 4150 },
    "2026-10-22": { "XAU": 0.0002462812, "USDXAU": 4060.4 },
    "2026-10-23": { "XAU": 0.0002495072, "USDXAU": 4007.9 }
  }
}

Contrairement à l'endpoint d'un seul jour, /timeframe ne comble pas les trous. Les métaux au comptant cessent de coter du vendredi 17:00 au dimanche 17:00, heure de New York : les week-ends et jours fériés des marchés peuvent donc manquer dans le résultat. Ne supposez pas une entrée par jour calendaire et, si vous mélangez des symboles aux calendriers de cotation différents, comme l'or et le bitcoin, vérifiez qu'une clé existe avant de la lire.

La période la plus longue qu'une seule requête peut couvrir dépend du plan :

PlanPériode maximale par requêteUnité de pagination toujours valide
Free30 joursUn mois civil
Lite et supérieurs365 joursUne année civile

La limite porte sur l'écart entre les deux dates : 2025-01-01 à 2025-01-31 respecte donc la limite du plan Free, et 2024-01-01 à 2024-12-31 celle des plans payants, même pour une année bissextile. Une période plus longue renvoie l'erreur 207 (timeframe_too_long) avec une upgrade_url. Chaque appel à /timeframe compte pour une requête, quel que soit le nombre de jours renvoyés.

Comparer deux dates : /change

Lorsque seule la variation entre deux jours vous intéresse, par exemple la performance depuis le début de l'année sur un tableau de bord, /change renvoie le taux de départ, le taux de fin, la variation absolue et la variation en pourcentage sans télécharger les jours intermédiaires :

bash
curl "https://metalapi.com/api/v1/change?start_date=2026-01-02&end_date=2026-10-23&currencies=XAU,XAG" \
  -H "X-API-Key: $METALAPI_KEY"
Exemple de réponse (or uniquement)
{
  "success": true,
  "change": true,
  "start_date": "2026-01-02",
  "end_date": "2026-10-23",
  "base": "USD",
  "rates": {
    "XAU": { "start_rate": 0.0002309682, "end_rate": 0.0002495072, "change": 0.000018539, "change_pct": 8.02665 },
    "USDXAU": { "start_rate": 4329.6, "end_rate": 4007.9, "change": -321.7, "change_pct": -7.43025 }
  }
}

Lisez la clé préfixée, USDXAU. La clé inverse XAU évolue en sens contraire : son change_pct a donc le signe opposé et une amplitude différente. Dans cet exemple, le cours de l'or a baissé de 7,43 % tandis que l'inverse a progressé de 8,03 %. Afficher le pourcentage de l'inverse comme variation du prix est une erreur qui arrive facilement en production.

/change a les mêmes limites de période que /timeframe. Comme /historical, il utilise la clôture la plus récente lorsqu'une date tombe un week-end ou un jour férié.

Bougies quotidiennes : /ohlc

/ohlc renvoie l'ouverture, le plus haut, le plus bas et la clôture d'un jour, dans les deux sens : USDXAU en dollars par once et XAU en inverse. Sans date, ou avec date=latest, il renvoie la barre du jour en cours.

bash
curl "https://metalapi.com/api/v1/ohlc?date=2026-10-23&currencies=XAU" \
  -H "X-API-Key: $METALAPI_KEY"
Exemple de réponse
{
  "success": true,
  "ohlc": true,
  "date": "2026-10-23",
  "base": "USD",
  "rates": {
    "USDXAU": { "open": 4042.2, "high": 4050.2, "low": 3989.1, "close": 4007.9 },
    "XAU": { "open": 0.000247390, "high": 0.000250683, "low": 0.000246901, "close": 0.000249507 }
  }
}

Notez que dans le bloc inverse, le plus haut provient du prix le plus bas et le plus bas du prix le plus haut, car l'inversion renverse l'ordre. Tracez le bloc USDXAU.

Chaque appel couvre un jour : un graphique en chandeliers sur une année complète demande donc une requête par jour de cotation. Pour les graphiques de long terme, les cours de clôture de /timeframe coûtent bien moins cher. Utilisez /ohlc pour les bougies récentes ou pour afficher la fourchette du jour à côté du prix actuel.

Pour des bougies hebdomadaires ou mensuelles sur de nombreuses années, vous pouvez aussi construire les barres à partir des clôtures quotidiennes stockées : l'ouverture est la première clôture de la période, la clôture la dernière, et le plus haut et le plus bas sont les extrêmes des clôtures. Elles ignorent les plus hauts et plus bas intrajournaliers : indiquez donc qu'elles sont calculées sur les clôtures. Elles conviennent néanmoins très bien aux graphiques de long terme.

Récupérer 25 ans d'historique, une année par requête

Avec une limite de 365 jours par requête, l'historique complet de l'or et de l'argent depuis 2000 demande un appel à /timeframe par année civile : 27 requêtes de 2000 à 2026. Cela tient largement dans le quota mensuel de n'importe quel plan payant. Sur le plan Free, des fenêtres de 30 jours nécessiteraient plus de 300 requêtes, environ trois fois son quota mensuel de 100 : un import complet n'y est donc pas réaliste, mais quelques mois d'historique le sont.

Ce script Python pagine par année, attend la fin de la limite par minute et écrit les clés de prix des deux métaux dans SQLite. Enregistrez-le sous backfill.py :

backfill.py
import os
import sqlite3
import time
from datetime import datetime, timezone

import requests

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

db = sqlite3.connect("metals.db")
db.execute(
    """create table if not exists daily_close (
         day    text not null,
         symbol text not null,
         base   text not null,
         price  real not null,
         primary key (day, symbol, base)
       )"""
)


def fetch_timeframe(start, end, symbols, base="USD"):
    """Clôtures quotidiennes entre start et end (inclus), indexées par date."""
    while True:
        r = session.get(
            f"{API}/timeframe",
            params={"start_date": start, "end_date": end, "currencies": ",".join(symbols), "base": base},
            timeout=30,
        )
        data = r.json()
        if data.get("success"):
            return data["rates"]
        err = data["error"]
        if err["code"] == 104:  # limite par minute : attendre, puis réessayer (non facturé)
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        if err["code"] == 300:  # aucune donnée dans cette fenêtre
            return {}
        raise RuntimeError(f"{err['code']} {err['type']}: {err['info']}")


def store(rates, symbols, base="USD"):
    rows = [
        (day, s, base, values[f"{base}{s}"])
        for day, values in rates.items()
        for s in symbols
        if f"{base}{s}" in values
    ]
    db.executemany("insert or replace into daily_close values (?, ?, ?, ?)", rows)
    db.commit()
    return len(rows)


def backfill(first_year, symbols=("XAU", "XAG"), base="USD"):
    today = datetime.now(timezone.utc).date()
    for year in range(first_year, today.year + 1):
        start = f"{year}-01-01"
        end = min(f"{year}-12-31", today.isoformat())
        n = store(fetch_timeframe(start, end, symbols, base), symbols, base)
        print(f"{year}: {n} rows")


if __name__ == "__main__":
    backfill(2000)

Le script peut être relancé sans risque : insert or replace écrase les lignes existantes, si bien qu'un import interrompu peut simplement être redémarré. L'année en cours s'arrête à la date UTC du jour, car une end_date future renvoie 205. Les limites par minute sont les plus basses sur les petits plans (10 requêtes par minute sur Free et 30 sur Lite) : c'est pourquoi le script respecte Retry-After sur l'erreur 104 au lieu d'échouer.

Il stocke USDXAU et USDXAG, c'est-à-dire les prix, et non les clés inverses. Si vous avez besoin de l'historique dans une autre devise, passez base, ou stockez aussi les cours en dollars des devises et convertissez localement à partir des lignes du même jour.

Stocker et mettre à jour les données

Les cours de clôture des jours passés ne changent pas : une copie locale est donc le moyen le plus économique et le plus rapide d'alimenter graphiques et rapports. Quelques règles la gardent correcte :

  • Indexez les lignes par jour, symbole et devise de base. La table ci-dessus utilise (day, symbol, base) comme clé primaire : récupérer deux fois une période écrase donc les lignes au lieu de les dupliquer.
  • Enregistrez ce que vous avez demandé. Stockez base, et unit si vous l'utilisez, avec les données. Mélanger des lignes au gramme et à l'once dans une même colonne est une cause classique de pics dans un graphique.
  • Complétez une fois par jour. Après l'import initial, une requête par jour suffit à tenir la table à jour. Récupérez les sept derniers jours, pour qu'une exécution manquée ou une clôture tardive soit comblée automatiquement.
  • Considérez le jour en cours comme provisoire. Seule la barre du jour en cours bouge encore. Laissez-la hors de l'archive, ou écrasez-la à l'exécution suivante comme le fait le script ci-dessous.
daily.py (à exécuter une fois par jour après 00:00 UTC)
from datetime import datetime, timedelta, timezone

from backfill import fetch_timeframe, store

SYMBOLS = ("XAU", "XAG")
today = datetime.now(timezone.utc).date()
start = (today - timedelta(days=7)).isoformat()
end = (today - timedelta(days=1)).isoformat()
print(store(fetch_timeframe(start, end, SYMBOLS), SYMBOLS), "rows updated")

À raison d'une requête par jour, la mise à jour coûte environ 30 requêtes par mois, ce qui laisse même de la marge sur le plan Free. Pour des archives plus volumineuses, le même schéma fonctionne avec PostgreSQL, un fichier Parquet ou n'importe quelle base de séries temporelles ; seule la fonction store change.

Avant de bâtir quoi que ce soit sur un import historique, vérifiez-le. Trois contrôles détectent la plupart des problèmes :

  • Nombre de lignes par année. Chaque année complète devrait compter environ une ligne par jour de cotation pour chaque métal. Une année avec beaucoup moins de lignes signifie qu'une requête a échoué ou n'a renvoyé aucune donnée.
  • Trous. Un week-end ordinaire laisse trois jours entre deux clôtures, un long week-end férié quatre. Tout écart plus long mérite un examen, et récupérer à nouveau cette fenêtre ne coûte qu'une requête.
  • Sauts. Une variation d'un jour à l'autre d'un facteur d'environ 31 ou 1 000 trahit des unités mélangées, et une valeur proche de zéro une clé inverse qui s'est glissée dans les données.

Les deux premiers contrôles tiennent en quelques lignes de SQLite :

checks.sql
-- Lignes par année : une année complète compte environ une ligne par jour de cotation
select substr(day, 1, 4) as year, symbol, count(*) as days from daily_close
 where base = 'USD' group by year, symbol order by year, symbol;

-- Écarts de plus de quatre jours entre deux clôtures stockées
select symbol, prev_day, day from (
  select symbol, day, lag(day) over (partition by symbol order by day) as prev_day
    from daily_close where base = 'USD'
) where julianday(day) - julianday(prev_day) > 4;

Tracer les données

Une fois les données stockées localement, les graphiques ne coûtent aucune requête. Ce script matplotlib trace le cours de l'or en dollars ainsi que le ratio or/argent, c'est-à-dire le nombre d'onces d'argent qu'achète une once d'or :

chart.py
import sqlite3
from datetime import date

import matplotlib.dates as mdates
import matplotlib.pyplot as plt

db = sqlite3.connect("metals.db")
rows = db.execute(
    """select g.day, g.price, s.price
         from daily_close g
         join daily_close s on s.day = g.day and s.base = g.base and s.symbol = 'XAG'
        where g.symbol = 'XAU' and g.base = 'USD'
        order by g.day"""
).fetchall()

days = [date.fromisoformat(day) for day, _, _ in rows]
gold = [g for _, g, _ in rows]
ratio = [g / s for _, g, s in rows]

fig, (top, bottom) = plt.subplots(2, 1, sharex=True, figsize=(10, 6))
top.plot(days, gold, linewidth=1)
top.set_ylabel("Gold, USD per troy oz")
bottom.plot(days, ratio, linewidth=1)
bottom.set_ylabel("Gold/silver ratio")
bottom.xaxis.set_major_locator(mdates.YearLocator(base=5))
fig.tight_layout()
fig.savefig("gold-silver.png", dpi=150)

Sur un site web, servez la série stockée depuis votre propre backend et dessinez-la dans le navigateur. Votre clé API reste sur le serveur, et les pages vues n'entament pas votre quota. Voici une page Chart.js minimale qui lit un tableau JSON d'objets { day, price } depuis votre propre endpoint :

gold-chart.html
<canvas id="gold" height="320"></canvas>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js"></script>
<script>
  // Votre propre endpoint, qui renvoie [{ "day": "2026-10-23", "price": 4007.9 }, ...]
  fetch("/api/gold-history")
    .then((res) => res.json())
    .then((points) => {
      new Chart(document.getElementById("gold"), {
        type: "line",
        data: {
          labels: points.map((p) => p.day),
          datasets: [{ label: "Gold, USD per troy oz", data: points.map((p) => p.price), pointRadius: 0, borderWidth: 1 }],
        },
        options: {
          animation: false,
          scales: { x: { ticks: { maxTicksLimit: 10 } } },
        },
      });
    });
</script>

Vingt-cinq ans de jours de cotation représentent plus de 6 000 points par série. Désactiver les marqueurs de points et l'animation, comme ci-dessus, garde le graphique réactif ; sur petit écran, envisagez de sous-échantillonner en clôtures hebdomadaires.

Le guide des données historiques propose un utilitaire JavaScript pour les périodes et un exemple de moyenne mobile, et la documentation de l'API liste tous les paramètres. Si vous travaillez avec des assistants IA, le serveur MCP de MetalAPI peut récupérer les mêmes séries temporelles depuis une conversation. Pour les clés, les erreurs et le cache en général, commencez par le guide de l'API du cours de l'or, et comparez les limites de période et les quotas sur la page des tarifs.

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