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%
Todos os artigos
OuroPrataDados históricosTutorial

Preços históricos de ouro e prata via API: 25 anos de dados diários

Preços diários de ouro e prata desde 2000 com /historical, /timeframe, /change e /ohlc: pagine longos intervalos por ano, armazene os dados e crie gráficos.

Por MetalAPI Team··10 min de leitura

Qual histórico está disponível

A MetalAPI mantém uma barra diária por símbolo e por dia, com abertura, máxima, mínima e fechamento. Para ouro e prata, isso significa mais de 25 anos de dados diários: o suficiente para traçar tendências de longo prazo, testar uma regra de precificação contra o passado ou reavaliar notas fiscais antigas pelo preço do dia.

CategoriaSímbolosHistórico diário desde
Metais preciososXAU, XAG, XPT, XPD2000
Metais industriais e energiaXCU, ALU, ZNC, HRC, WTI, BRENT e outros2000
Moedas57, incluindo USD, EUR, GBP e JPY04/01/1999
Criptomoedas13, incluindo BTC e ETH2014

Todos os planos podem consultar o histórico completo. Os planos diferem apenas na quantidade de dias que uma única requisição de intervalo pode abranger. As datas usam o formato YYYY-MM-DD em UTC, e os valores de cada dia são os preços de fechamento daquele dia. Uma data anterior ao início do histórico de um símbolo retorna o erro 300 (no_results).

Os preços dos metais anteriores a outubro de 2026 são derivados dos contratos futuros de vencimento mais próximo, então podem diferir ligeiramente do preço à vista do mesmo dia. Tenha isso em mente ao comparar valores antigos com preços à vista de outra fonte.

Quatro endpoints cobrem o trabalho com dados históricos: /historical para um dia, /timeframe para um intervalo, /change para a variação entre dois dias e /ohlc para o candle de um dia. As seções abaixo tratam de cada um deles e depois montam um pequeno arquivo local com um gráfico.

Um dia: /historical

Para obter os preços de fechamento de um único dia, coloque a data no caminho. /historical?date=YYYY-MM-DD faz o mesmo, e /yesterday é um atalho para o dia UTC anterior:

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

A resposta tem o mesmo formato de /latest, mais historical: true. Estes são os valores de exemplo da referência da API:

Exemplo de resposta
{
  "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 é o preço de uma onça troy de ouro na moeda base, e XAU é o seu inverso. base e unit funcionam como de costume, então base=EUR&unit=gram retorna euros por grama naquele dia.

Fins de semana e feriados não quebram a requisição: para um dia sem negociação, é retornado o fechamento do pregão mais recente. O date da resposta continua sendo o dia que você pediu, então, se precisar saber se os mercados estavam abertos, mantenha um calendário de negociação do seu lado. Uma data futura retorna o erro 205 (invalid_date).

Intervalos de datas: /timeframe

Para uma série, /timeframe retorna uma entrada por dia entre start_date e end_date, ambos inclusivos, indexada pela data. /timeseries é um alias que marca a resposta com timeseries: true em vez 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"
Exemplo de resposta
{
  "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 }
  }
}

Ao contrário do endpoint de um único dia, /timeframe não preenche lacunas. Os metais à vista param de ser negociados de sexta-feira às 17:00 até domingo às 17:00, no horário de Nova York, então fins de semana e feriados de mercado podem estar ausentes do resultado. Não presuma uma entrada por dia do calendário e, se você misturar símbolos com calendários de negociação diferentes, como ouro e bitcoin, verifique se uma chave existe antes de lê-la.

O intervalo máximo que uma única requisição pode abranger depende do plano:

PlanoIntervalo máximo por requisiçãoUnidade de paginação que sempre cabe
Free30 diasUm mês do calendário
Lite e superiores365 diasUm ano do calendário

O limite é a distância entre as duas datas, então de 2025-01-01 a 2025-01-31 cabe no limite do Free e de 2024-01-01 a 2024-12-31 cabe no limite dos planos pagos, mesmo em um ano bissexto. Um intervalo maior retorna o erro 207 (timeframe_too_long) com uma upgrade_url. Cada chamada a /timeframe conta como uma requisição, não importa quantos dias ela retorne.

Comparando duas datas: /change

Quando você só precisa da variação entre dois dias, como o desempenho no acumulado do ano em um painel, /change retorna a cotação inicial, a cotação final, a variação absoluta e a variação percentual sem baixar os dias intermediários:

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"
Exemplo de resposta (apenas ouro)
{
  "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 }
  }
}

Leia a chave com prefixo, USDXAU. A chave inversa XAU se move no sentido contrário, então o seu change_pct tem o sinal oposto e uma magnitude diferente: neste exemplo, o preço do ouro caiu 7,43%, enquanto o inverso subiu 8,03%. Exibir o percentual do inverso como a variação do preço é um erro fácil de ir para produção.

/change tem os mesmos limites de intervalo que /timeframe. Assim como /historical, ele usa o fechamento mais recente quando uma data cai em um fim de semana ou feriado.

Candles diários: /ohlc

/ohlc retorna a abertura, a máxima, a mínima e o fechamento de um dia, nas duas direções: USDXAU em dólares por onça e XAU como o inverso. Sem date, ou com date=latest, ele retorna a barra do dia atual até o momento.

bash
curl "https://metalapi.com/api/v1/ohlc?date=2026-10-23&currencies=XAU" \
  -H "X-API-Key: $METALAPI_KEY"
Exemplo de resposta
{
  "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 }
  }
}

Observe que, no bloco inverso, a máxima vem do menor preço e a mínima do maior preço, porque a inversão inverte a ordem. Use o bloco USDXAU nos gráficos.

Cada chamada cobre um dia, então um gráfico de candlestick de um ano inteiro exige uma requisição por pregão. Para gráficos longos, os preços de fechamento de /timeframe saem muito mais baratos. Use /ohlc para candles recentes ou para mostrar a amplitude de negociação do dia ao lado do preço atual.

Para candles semanais ou mensais ao longo de muitos anos, você também pode montar barras a partir dos fechamentos diários armazenados: a abertura é o primeiro fechamento do período, o fechamento é o último, e a máxima e a mínima são os extremos dos fechamentos. Elas não capturam máximas e mínimas intradiárias, então identifique-as como baseadas em fechamentos, mas servem bem para gráficos de longo prazo.

Carregando 25 anos de histórico, um ano por requisição

Com um limite de 365 dias por requisição, o histórico completo de ouro e prata desde 2000 exige uma chamada a /timeframe por ano do calendário: 27 requisições de 2000 a 2026. Isso cabe com folga na cota mensal de qualquer plano pago. No plano Free, janelas de 30 dias exigiriam mais de 300 requisições, cerca de três vezes a sua cota mensal de 100, então uma carga histórica completa não é viável nele; alguns meses de histórico, sim.

Este script em Python pagina por ano, respeita o limite por minuto e grava as chaves de preço dos dois metais no SQLite. Salve-o como 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"):
    """Fechamentos diários entre start e end (inclusive), indexados pela data."""
    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 por minuto: espera e tenta de novo (não é cobrado)
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        if err["code"] == 300:  # sem dados nesta janela
            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)

O script pode ser executado novamente com segurança: insert or replace sobrescreve as linhas que já existem, então uma carga interrompida pode simplesmente ser reiniciada. O ano atual termina na data UTC de hoje, porque um end_date futuro retorna 205. Os limites por minuto são menores nos planos mais baratos (10 requisições por minuto no Free e 30 no Lite), e é por isso que o script respeita o Retry-After no erro 104 em vez de falhar.

Ele armazena USDXAU e USDXAG, os preços, e não as chaves inversas. Se você precisar do histórico em outra moeda, passe base ou armazene também os preços em dólar das moedas e faça a conversão localmente a partir das linhas do mesmo dia.

Armazenando e atualizando os dados

Os preços de fechamento de dias passados não mudam, então uma cópia local é a forma mais barata e rápida de servir gráficos e relatórios. Algumas regras a mantêm correta:

  • Indexe as linhas por dia, símbolo e base. A tabela acima usa (day, symbol, base) como chave primária, então buscar um intervalo duas vezes sobrescreve as linhas em vez de duplicá-las.
  • Registre o que você solicitou. Armazene base, e unit se você a usar, junto com os dados. Misturar linhas por grama e por onça em uma mesma coluna é uma causa clássica de picos em um gráfico.
  • Complete os dados uma vez por dia. Depois da carga histórica, uma requisição por dia mantém a tabela atualizada. Busque os últimos sete dias, para que uma execução perdida ou um fechamento atrasado seja preenchido automaticamente.
  • Trate o dia de hoje como provisório. Só a barra do dia atual ainda está mudando. Deixe-a fora do arquivo ou sobrescreva-a na próxima execução, como faz o script abaixo.
daily.py (execute uma vez por dia, apó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")

Com uma requisição por dia, a atualização custa cerca de 30 requisições por mês, o que ainda deixa espaço até no plano Free. Para arquivos maiores, o mesmo padrão funciona com PostgreSQL, um arquivo Parquet ou qualquer banco de dados de séries temporais; só a função store muda.

Antes de construir qualquer coisa sobre uma carga histórica, verifique-a. Três verificações pegam a maioria dos problemas:

  • Quantidade de linhas por ano. Todo ano completo deve ter aproximadamente uma linha por pregão para cada metal. Um ano com muito menos linhas indica que uma requisição falhou ou não retornou dados.
  • Lacunas. Um fim de semana comum deixa três dias entre dois fechamentos, e um feriado prolongado, quatro. Qualquer intervalo maior merece atenção, e buscar aquela janela de novo custa uma única requisição.
  • Saltos. Uma variação de um dia para o outro por um fator de cerca de 31 ou 1.000 indica unidades misturadas, e um valor próximo de zero indica que uma chave inversa entrou por engano.

As duas primeiras são consultas curtas no SQLite:

checks.sql
-- Linhas por ano: um ano completo tem aproximadamente uma linha por pregão
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;

-- Lacunas de mais de quatro dias entre fechamentos armazenados
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;

Criando gráficos com os dados

Com os dados armazenados localmente, os gráficos não custam nenhuma requisição. Este script com matplotlib traça o preço do ouro em dólares junto com a relação ouro/prata, o número de onças de prata que uma onça de ouro compra:

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)

Em um site, sirva a série armazenada a partir do seu próprio backend e desenhe o gráfico no navegador. A sua chave de API fica no servidor, e as visualizações de página não consomem a sua cota. Uma página mínima com Chart.js que lê um array JSON de objetos { day, price } do seu próprio 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>
  // O seu próprio endpoint, que retorna [{ "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>

Vinte e cinco anos de pregões são mais de 6.000 pontos por série. Desativar os marcadores de pontos e a animação, como acima, mantém o gráfico responsivo; em telas pequenas, considere reduzir a amostragem para fechamentos semanais.

O guia de dados históricos traz um helper de intervalos em JavaScript e um exemplo de média móvel, e a documentação da API lista todos os parâmetros. Se você trabalha com assistentes de IA, o servidor MCP da MetalAPI pode buscar as mesmas séries temporais a partir de um chat. Para chaves, erros e cache em geral, comece pelo guia da API de preços do ouro e compare os limites de intervalo e as cotas na página de preços.

Experimente com seus próprios dados

Crie uma conta grátis e faça sua primeira requisição em menos de dois minutos. 100 requisições por mês, sem cartão de crédito.

Continue lendo