Исторические цены на золото и серебро через API: 25 лет дневных данных
Дневные исторические цены на золото и серебро с 2000 года через /historical, /timeframe, /change и /ohlc: загрузка по годам, хранение данных и графики.
Какая история доступна
MetalAPI хранит по одному дневному бару на каждый символ и день — с ценами открытия, максимума, минимума и закрытия. Для золота и серебра это более 25 лет дневных данных: достаточно, чтобы строить графики долгосрочных трендов, проверять правило ценообразования на прошлых данных или переоценивать старые счета по цене соответствующего дня.
| Категория | Символы | Дневная история с |
|---|---|---|
| Драгоценные металлы | XAU, XAG, XPT, XPD | 2000 |
| Промышленные металлы и энергоносители | XCU, ALU, ZNC, HRC, WTI, BRENT и другие | 2000 |
| Валюты | 57, включая USD, EUR, GBP и JPY | 4 января 1999 |
| Криптовалюты | 13, включая BTC и ETH | 2014 |
На любом тарифе доступна вся история. Тарифы различаются только тем, сколько дней может охватывать один запрос диапазона. Даты задаются в формате YYYY-MM-DD по UTC, а значения за каждый день — это цены закрытия этого дня. Дата раньше начала истории символа возвращает ошибку 300 (no_results).
Цены металлов до октября 2026 года получены по фьючерсам ближайшего месяца, поэтому могут немного отличаться от спотовой цены того же дня. Учитывайте это, когда сравниваете старые значения со спотовыми ценами из другого источника.
Для работы с историей есть четыре эндпоинта: /historical — для одного дня, /timeframe — для диапазона, /change — для изменения между двумя днями и /ohlc — для дневной свечи. Ниже мы разберём их по очереди, а затем построим небольшой локальный архив с графиком.
Один день: /historical
Чтобы получить цены закрытия за один день, укажите дату в пути. /historical?date=YYYY-MM-DD работает так же, а /yesterday — сокращение для предыдущего дня по UTC:
curl "https://metalapi.com/api/v1/2024-01-15?currencies=XAU,XAG" \
-H "X-API-Key: $METALAPI_KEY"Ответ имеет ту же структуру, что и у /latest, плюс historical: true. Вот значения из примера в справочнике 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 — цена одной тройской унции золота в базовой валюте, а XAU — обратная величина. base и unit работают как обычно, поэтому base=EUR&unit=gram вернёт цену в евро за грамм на этот день.
Выходные и праздники не ломают запрос: для дня без торгов возвращается цена закрытия последнего торгового дня. При этом date в ответе — по-прежнему тот день, который вы запросили, поэтому, если вам нужно знать, работали ли рынки, ведите торговый календарь на своей стороне. Дата в будущем возвращает ошибку 205 (invalid_date).
Диапазоны дат: /timeframe
Для рядов данных /timeframe возвращает по одной записи на каждый день между start_date и end_date включительно, с датой в качестве ключа. /timeseries — псевдоним, который помечает ответ флагом timeseries: true вместо 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 }
}
}В отличие от эндпоинта для одного дня, /timeframe не заполняет пропуски. Торги спотовыми металлами прекращаются с 17:00 пятницы до 17:00 воскресенья по нью-йоркскому времени, поэтому выходные и биржевые праздники могут отсутствовать в результате. Не рассчитывайте на одну запись на каждый календарный день, а если смешиваете символы с разными торговыми календарями, например золото и биткоин, проверяйте наличие ключа, прежде чем его читать.
Максимальный диапазон одного запроса зависит от тарифа:
| Тариф | Максимальный диапазон на запрос | Шаг разбиения, который всегда укладывается в лимит |
|---|---|---|
| Free | 30 дней | Один календарный месяц |
| Lite и выше | 365 дней | Один календарный год |
Ограничение — это расстояние между двумя датами, поэтому диапазон с 2025-01-01 по 2025-01-31 укладывается в лимит Free, а с 2024-01-01 по 2024-12-31 — в лимит платных тарифов, даже в високосный год. Более длинный диапазон возвращает ошибку 207 (timeframe_too_long) с полем upgrade_url. Каждый вызов /timeframe считается одним запросом, сколько бы дней он ни возвращал.
Сравнение двух дат: /change
Если вам нужно только изменение между двумя днями, например доходность с начала года на дашборде, /change возвращает начальный курс, конечный курс, абсолютное и процентное изменение, не загружая промежуточные дни:
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 }
}
}Читайте ключ с префиксом — USDXAU. Обратный ключ XAU движется в противоположную сторону, поэтому его change_pct имеет противоположный знак и другую величину: в этом примере цена золота упала на 7,43%, а обратная величина выросла на 8,03%. Показать процент обратной величины как изменение цены — ошибка, которую легко выпустить в продакшен.
У /change те же ограничения диапазона, что и у /timeframe. Как и /historical, он берёт последнюю цену закрытия, если дата приходится на выходной или праздник.
Дневные свечи: /ohlc
/ohlc возвращает цены открытия, максимума, минимума и закрытия за один день в обоих направлениях: USDXAU — в долларах за унцию и XAU — как обратную величину. Без date или с date=latest возвращается бар текущего дня на данный момент.
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 }
}
}Обратите внимание: в обратном блоке максимум получается из самой низкой цены, а минимум — из самой высокой, потому что обращение меняет порядок на противоположный. Стройте график по блоку USDXAU.
Каждый вызов охватывает один день, поэтому свечной график за целый год требует одного запроса на каждый торговый день. Для длинных графиков цены закрытия из /timeframe обходятся гораздо дешевле. Используйте /ohlc для недавних свечей или чтобы показать дневной диапазон торгов рядом с текущей ценой.
Недельные или месячные свечи за много лет можно также строить из сохранённых дневных цен закрытия: открытие — первая цена закрытия периода, закрытие — последняя, а максимум и минимум — крайние значения цен закрытия. Внутридневные максимумы и минимумы в них не попадают, поэтому помечайте такие бары как построенные по ценам закрытия, но для графиков на длинных интервалах они вполне подходят.
Загрузка 25 лет истории: один год на запрос
При лимите 365 дней на запрос вся история золота и серебра с 2000 года загружается одним вызовом /timeframe на каждый календарный год: 27 запросов за 2000–2026 годы. Это легко укладывается в месячную квоту любого платного тарифа. На тарифе Free окна по 30 дней потребовали бы более 300 запросов — примерно втрое больше его месячной квоты в 100 запросов, поэтому полная загрузка там непрактична, а вот история за несколько месяцев — вполне.
Этот скрипт на Python проходит по годам, выжидает при достижении поминутного лимита и записывает ключи цен обоих металлов в SQLite. Сохраните его как 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"):
"""Daily closes between start and end (inclusive), keyed by 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: # per-minute limit: wait, then retry (not billed)
time.sleep(int(r.headers.get("Retry-After", "60")))
continue
if err["code"] == 300: # no data in this window
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)Скрипт можно безопасно запускать повторно: insert or replace перезаписывает уже существующие строки, поэтому прерванную загрузку можно просто начать заново. Текущий год заканчивается сегодняшней датой по UTC, потому что end_date в будущем возвращает 205. Поминутные лимиты ниже всего на младших тарифах (10 запросов в минуту на Free и 30 на Lite), поэтому при ошибке 104 скрипт учитывает Retry-After, а не завершается с ошибкой.
Скрипт сохраняет USDXAU и USDXAG — цены, а не обратные ключи. Если вам нужна история в другой валюте, передайте base или сохраняйте также долларовые цены валют и пересчитывайте локально по строкам того же дня.
Хранение и обновление данных
Цены закрытия прошедших дней не меняются, поэтому локальная копия — самый дешёвый и быстрый способ отдавать графики и отчёты. Несколько правил помогают сохранить её корректной:
- Ключ строки — день, символ и базовая валюта. Таблица выше использует
(day, symbol, base)в качестве первичного ключа, поэтому повторная загрузка диапазона перезаписывает строки, а не дублирует их. - Записывайте, что именно вы запрашивали. Храните вместе с данными
base, а такжеunit, если вы его используете. Смешение строк с ценой за грамм и за унцию в одном столбце — классическая причина скачков на графике. - Дозагружайте данные раз в день. После первичной загрузки таблицу поддерживает в актуальном состоянии один запрос в день. Запрашивайте последние семь дней, чтобы пропущенный запуск или запоздавшая цена закрытия заполнялись автоматически.
- Считайте сегодняшний день предварительным. Меняется только бар текущего дня. Не включайте его в архив или перезаписывайте при следующем запуске, как это делает скрипт ниже.
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")При одном запросе в день дозагрузка стоит около 30 запросов в месяц, так что для неё хватит даже тарифа Free. Для архивов побольше тот же подход работает с PostgreSQL, файлом Parquet или любой базой данных временных рядов; меняется только функция store.
Прежде чем строить что-либо на загруженных данных, проверьте их. Три проверки выявляют большинство проблем:
- Количество строк по годам. В каждом полном году должно быть примерно по одной строке на торговый день для каждого металла. Год с намного меньшим числом строк означает, что запрос завершился ошибкой или не вернул данных.
- Пропуски. Обычные выходные дают разрыв в три дня между двумя ценами закрытия, а длинные праздничные выходные — в четыре. Всё, что длиннее, стоит проверить, а повторная загрузка этого окна стоит всего один запрос.
- Скачки. Изменение за день примерно в 31 или 1 000 раз указывает на смешение единиц, а значение, близкое к нулю, — на то, что затесался обратный ключ.
Первые две проверки — короткие запросы в SQLite:
-- Rows per year: a complete year has roughly one row per trading day
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;
-- Gaps of more than four days between stored closes
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;Графики
Когда данные хранятся локально, графики не расходуют запросы. Этот скрипт на matplotlib строит цену золота в долларах вместе с соотношением золота и серебра — количеством унций серебра, которое можно купить на одну унцию золота:
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)На сайте отдавайте сохранённые ряды со своего бэкенда и рисуйте график в браузере. Ваш ключ API остаётся на сервере, а просмотры страниц не расходуют квоту. Минимальная страница на Chart.js, которая читает JSON-массив объектов { day, price } с вашего собственного эндпоинта:
<canvas id="gold" height="320"></canvas>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js"></script>
<script>
// Your own endpoint, returning [{ "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>Двадцать пять лет торговых дней — это более 6 000 точек на ряд. Если отключить маркеры точек и анимацию, как в примере выше, график останется отзывчивым; на маленьких экранах подумайте о прореживании до недельных цен закрытия.
В руководстве по историческим данным есть вспомогательная функция на JavaScript для диапазонов и пример скользящей средней, а в документации API перечислены все параметры. Если вы работаете с ИИ-ассистентами, MCP-сервер MetalAPI может получить те же временные ряды прямо в чате. Ключи, ошибки и кэширование в целом описаны в руководстве по API цен на золото, а лимиты диапазонов и квоты можно сравнить на странице тарифов.
Попробуйте на своих данных
Создайте бесплатный аккаунт и отправьте первый запрос меньше чем за две минуты. 100 запросов в месяц, без банковской карты.
Читайте также
API цен на золото: полное руководство для разработчиков
Как работать с API цен на золото: получить ключ, вызвать API из curl, JavaScript и Python, правильно читать курсы, работать с историей, ошибками, квотами и кэшем.
Как добавить на сайт виджет с актуальной ценой золота
Виджет с актуальной ценой золота для WordPress, Shopify или HTML: создайте его в личном кабинете MetalAPI, выберите символы, макет и тему и вставьте две строки.
Цены на золото по каратам: 24K, 22K, 18K и как их рассчитать
Что означают 24K, 22K и 18K, как с ними связаны пробы 916 и 750, как рассчитать цену грамма золота по каратам и как получить её через API цен на золото.