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.
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égorie | Symboles | Historique quotidien depuis |
|---|---|---|
| Métaux précieux | XAU, XAG, XPT, XPD | 2000 |
| Métaux industriels et énergie | XCU, ALU, ZNC, HRC, WTI, BRENT et autres | 2000 |
| Devises | 57, dont USD, EUR, GBP et JPY | 4 janvier 1999 |
| Cryptomonnaies | 13, dont BTC et ETH | 2014 |
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 :
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 :
{
"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.
curl "https://metalapi.com/api/v1/timeframe?start_date=2026-10-19&end_date=2026-10-23¤cies=XAU" \
-H "X-API-Key: $METALAPI_KEY"{
"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 :
| Plan | Période maximale par requête | Unité de pagination toujours valide |
|---|---|---|
| Free | 30 jours | Un mois civil |
| Lite et supérieurs | 365 jours | Une 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 :
curl "https://metalapi.com/api/v1/change?start_date=2026-01-02&end_date=2026-10-23¤cies=XAU,XAG" \
-H "X-API-Key: $METALAPI_KEY"{
"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.
curl "https://metalapi.com/api/v1/ohlc?date=2026-10-23¤cies=XAU" \
-H "X-API-Key: $METALAPI_KEY"{
"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 :
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, etunitsi 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.
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 :
-- 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 :
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 :
<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
API du cours de l'or : le guide complet pour les développeurs
Utiliser une API du cours de l'or : obtenir une clé, l'appeler en curl, JavaScript et Python, bien lire les taux, gérer historique, erreurs, quotas et cache.
Ajouter un widget de prix de l'or en direct à votre site web
Ajoutez un widget de prix de l'or en direct à WordPress, Shopify ou une page HTML : créez-le dans le tableau de bord MetalAPI, réglez-le et collez deux lignes.
Prix de l'or par carat : 24K, 22K, 18K et comment les calculer
Ce que signifient 24K, 22K et 18K, le lien avec les titres 916 et 750, comment calculer le prix au gramme de l'or par carat et l'obtenir via une API.