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%
全部文章
黄金白银历史数据教程

通过 API 获取黄金和白银历史价格:25 年每日数据

使用 /historical、/timeframe、/change 和 /ohlc 获取 2000 年以来的黄金和白银每日历史价格,按年分页拉取长区间数据,并在本地存储和绘制图表。

作者:MetalAPI Team··阅读约 10 分钟

可用的历史数据

MetalAPI 为每个品种每天保存一条日线数据,包含开盘价、最高价、最低价和收盘价。对黄金和白银而言,这意味着超过 25 年的每日数据:足以绘制长期走势图、用历史数据检验定价规则,或按当日价格重新估值旧发票。

类别品种每日历史数据起始时间
贵金属XAU、XAG、XPT、XPD2000 年
工业金属和能源XCU、ALU、ZNC、HRC、WTI、BRENT 等2000 年
货币57 种,包括 USD、EUR、GBP 和 JPY1999-01-04
加密货币13 种,包括 BTC 和 ETH2014 年

所有套餐都可以查询完整的历史数据,各套餐的区别仅在于单次区间请求可覆盖的天数。日期采用 YYYY-MM-DD 格式(UTC),每天的数值为当天的收盘价。如果日期早于某个品种历史数据的起始时间,将返回错误 300(no_results)。

2026 年 10 月之前的金属价格来源于近月期货,因此可能与同一天的现货价格略有差异。将旧数据与其他来源的现货价格进行比较时,请注意这一点。

四个端点涵盖了历史数据的各类用途:/historical 查询单日,/timeframe 查询区间,/change 查询两个日期之间的变化,/ohlc 查询某一天的 K 线。下面将依次介绍,然后搭建一个小型本地数据存档,并在此基础上绘制图表。

单日数据:/historical

要获取某一天的收盘价,将日期放在路径中即可。/historical?date=YYYY-MM-DD 的效果相同,/yesterday 则是查询前一个 UTC 日的快捷方式:

bash
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 是 1 金衡盎司黄金以基础货币计的价格,XAU 是它的倒数。base 和 unit 的用法与平常一样,因此 base=EUR&unit=gram 会返回当天每克黄金的欧元价格。

周末和节假日不会导致请求失败:对于没有交易的日子,API 会返回最近一个交易日的收盘价。响应中的 date 仍是您请求的日期,因此如果需要知道当天市场是否开放,请在您这边维护一份交易日历。未来的日期会返回错误 205(invalid_date)。

日期区间:/timeframe

如需获取一个序列,/timeframe 会为 start_date 与 end_date 之间(含首尾两天)的每一天返回一条数据,并以日期为键。/timeseries 是一个别名,它在响应中标记 timeseries: true 而不是 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"
响应示例
{
  "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 之间停止交易,因此结果中可能缺少周末和市场假日。不要假设每个自然日都有一条数据;如果混合查询交易日历不同的品种(例如黄金和比特币),请在读取前先检查键是否存在。

单次请求可覆盖的最长区间取决于套餐:

套餐单次请求最长区间始终符合限制的分页单位
Free30 天一个自然月
Lite 及以上365 天一个自然年

该限制指的是两个日期之间的间隔,因此 2025-01-01 至 2025-01-31 符合 Free 套餐的限制,2024-01-01 至 2024-12-31 即使在闰年也符合付费套餐的限制。区间更长时将返回错误 207(timeframe_too_long),并附带 upgrade_url。每次 /timeframe 调用都只计为一次请求,无论返回多少天的数据。

比较两个日期:/change

如果您只需要两天之间的变化(例如数据看板上的年初至今表现),/change 会返回起始价、结束价、绝对变化和百分比变化,无需下载中间每一天的数据:

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"
响应示例(仅黄金)
{
  "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 一样,当日期落在周末或节假日时,它会使用最近一个收盘价。

每日 K 线:/ohlc

/ohlc 返回某一天的开盘价、最高价、最低价和收盘价,并同时提供两个方向:USDXAU 为每盎司的美元价格,XAU 为其倒数。省略 date 或传入 date=latest,则返回今天截至目前的数据。

bash
curl "https://metalapi.com/api/v1/ohlc?date=2026-10-23&currencies=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 区块。

每次调用只覆盖一天,因此绘制一整年的 K 线图需要为每个交易日发送一次请求。对于长周期图表,使用 /timeframe 的收盘价要划算得多。/ohlc 适合用于近期的 K 线,或在当前价格旁边显示当天的交易区间。

如需多年的周 K 或月 K,也可以用已存储的每日收盘价自行构建:开盘价取该周期的第一个收盘价,收盘价取最后一个收盘价,最高价和最低价取期间收盘价的极值。这样会遗漏日内的最高价和最低价,因此请将其标注为基于收盘价,但用于长周期图表完全没有问题。

回填 25 年数据,每次请求一年

在单次请求最多 365 天的限制下,获取 2000 年以来完整的黄金和白银历史数据,只需为每个自然年调用一次 /timeframe:2000 年至 2026 年共 27 次请求,任何付费套餐的每月配额都绰绰有余。在 Free 套餐中,按 30 天的窗口需要 300 多次请求,约为其每月 100 次配额的三倍,因此在 Free 套餐中完整回填并不现实,但获取几个月的历史数据是可行的。

下面的 Python 脚本按年分页,遇到每分钟限制时等待,并将两种金属的价格键写入 SQLite。将其保存为 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"):
    """start 与 end 之间(含首尾)的每日收盘价,以日期为键。"""
    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:  # 每分钟限制:等待后重试(不计费)
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        if err["code"] == 300:  # 该窗口内没有数据
            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。最小的几个套餐每分钟限制也最低(Free 为每分钟 10 次,Lite 为每分钟 30 次),因此脚本在遇到错误 104 时会遵循 Retry-After 等待,而不是直接失败。

脚本存储的是价格 USDXAU 和 USDXAG,而不是倒数键。如果需要以其他货币计的历史数据,请传入 base,或者同时存储各货币的美元价格,再用同一天的数据在本地换算。

存储和更新数据

过去日期的收盘价不会改变,因此本地副本是提供图表和报表最便宜、最快捷的方式。遵循以下几条规则可以保证数据正确:

  • 以日期、品种和基础货币作为行的键。上面的表以 (day, symbol, base) 为主键,因此重复获取同一区间会覆盖已有的行,而不会产生重复数据。
  • 记录您请求时使用的参数。将 base(如果使用了 unit,也包括 unit)与数据一同保存。在同一列中混合每克和每盎司的数据,是图表中出现尖刺的典型原因。
  • 每天补充一次。回填完成后,每天一次请求即可让表保持最新。请获取最近七天的数据,这样漏跑的任务或延迟的收盘价都会被自动补齐。
  • 将今天的数据视为临时数据。只有当天的数据仍在变化。不要将其放入存档,或者像下面的脚本那样在下次运行时覆盖它。
daily.py(每天 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")

每天一次请求,补充数据每月只需约 30 次请求,即使在 Free 套餐中也还有余量。对于更大的存档,同样的模式也适用于 PostgreSQL、Parquet 文件或任何时序数据库,只需修改 store 函数即可。

在基于回填数据构建任何功能之前,请先检查数据。以下三项检查可以发现大多数问题:

  • 每年的行数。每个完整年份中,每种金属的行数应大致等于交易日数。如果某一年的行数少得多,说明有请求失败或未返回数据。
  • 缺口。普通周末会使两个收盘价之间相隔三天,长假周末则相隔四天。间隔更长的情况都值得检查一下,而重新获取该窗口只需一次请求。
  • 跳变。如果相邻两天的价格相差约 31 倍或 1,000 倍,说明混用了单位;如果数值接近于零,说明混入了倒数键。

前两项检查在 SQLite 中只需简短的查询:

checks.sql
-- 每年的行数:完整年份大致每个交易日一行
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;

-- 已存储的收盘价之间超过四天的缺口
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 脚本绘制以美元计的金价,以及金银比(1 盎司黄金可以买到的白银盎司数):

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)

在网站上,请从您自己的后端提供已存储的数据序列,并在浏览器中绘制。这样 API 密钥始终保存在服务器上,页面浏览也不会消耗您的配额。下面是一个极简的 Chart.js 页面,它从您自己的端点读取由 { day, price } 对象组成的 JSON 数组:

gold-chart.html
<canvas id="gold" height="320"></canvas>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js"></script>
<script>
  // 您自己的端点,返回 [{ "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>

25 年的交易日意味着每个序列有 6,000 多个数据点。像上面那样关闭数据点标记和动画,可以让图表保持流畅;在小屏幕上,可以考虑降采样为每周收盘价。

历史数据指南提供了一个 JavaScript 区间辅助函数和移动平均线示例,API 文档列出了所有参数。如果您使用 AI 助手,MetalAPI MCP 服务器可以在对话中获取同样的时间序列。关于密钥、错误和缓存的通用知识,请从黄金价格 API 指南开始阅读;各套餐的区间限制和配额可在定价页面中比较。

用您自己的数据试一试

创建免费账户,两分钟内即可发起第一次请求。每月 100 次请求,无需信用卡。

继续阅读