CMS e plataformas 13 min de leitura

Extração de cotações da bolsa: APIs, fontes, código e armazenamento de dados

De onde obter cotações da bolsa: APIs de mercado, portais financeiros, exemplos de código em cinco linguagens e armazenamento do histórico de preços.

EW
Equipe Web-Scraping.biz
Coleta de dados para as demandas do negócio
Publicado: 18 março 2025

Este é o material gêmeo do artigo «Scraping de taxas de câmbio». Boa parte daquele texto migra para cá quase sem mudanças — em especial a disciplina de tipos de dados (nada de float para os preços) e as práticas de cache. Mas as cotações da bolsa têm especificidades próprias: tickers e praças de negociação, pregões e feriados, eventos corporativos (splits e dividendos) e — a grande diferença em relação às taxas dos bancos centrais — o licenciamento dos dados. A informação de bolsa em tempo real é regulada pelas bolsas e pelos órgãos reguladores e, ao contrário da taxa de câmbio oficial, de publicação livre, nem de longe pode ser sempre obtida de graça, muito menos redistribuída.


1. O que é uma «cotação» e que dados existem

Dependendo da tarefa, «cotação» significa coisas diferentes, e isso é a primeira coisa a definir:

  • Last / preço atual — o preço da última operação. O que o widget de «preço da ação agora» mostra.
  • Barra OHLCV (candle) — Open, High, Low, Close e Volume de um intervalo (minuto, hora, dia). A base dos gráficos e dos backtests.
  • Bid/Ask (livro de ofertas) — os melhores preços de compra e venda. São necessários para trading; costumam ser os dados mais «caros» e mais sujeitos a licença.
  • EOD (end-of-day) — os preços de fechamento do pregão. Baratos ou gratuitos; servem para análise e para o acompanhamento de carteiras.
  • Adjusted close — o fechamento ajustado por splits e dividendos, para que o histórico seja contínuo.

E a bifurcação-chave pelo «frescor»: tempo real → atraso de 15--20 minutos → end-of-day. Quanto mais frescos e granulares os dados, mais rígida a licença e mais alto o preço.


2. As bolsas como fonte primária

A fonte primária das cotações são as próprias bolsas: NYSE e NASDAQ nos Estados Unidos, as praças da Euronext, a Bolsa de Londres, a Deutsche Börse ou, no Brasil, a B3 (a bolsa de São Paulo). Mas, ao contrário dos bancos centrais, as bolsas quase nunca oferecem de graça uma API pública: seus dados em tempo real são um produto licenciado, distribuído por meio de distribuidores autorizados (vendors), e o que sai gratuitamente em sites e portais costuma vir com um atraso de 15--20 minutos. Para o desenvolvedor, o caminho prático são as APIs da próxima seção.

Bolsa O que negocia Acesso aos dados
NYSE / NASDAQ (EUA) ações, ETFs tempo real com licença via distribuidores; atraso de ~15 min em portais gratuitos
B3 — a bolsa brasileira ações brasileiras, índice Ibovespa cotações com atraso no site; tempo real sob licença, via distribuidores autorizados
Euronext, LSE, Deutsche Börse ações europeias mesmo esquema: distribuidores e licenças
BMV (México) e outras praças latino-americanas mercados latino-americanos site com atraso; dados completos via distribuidores

Uma ponte conveniente para o artigo de moedas: muitas das APIs da próxima seção (Finnhub, Twelve Data, Alpha Vantage) não cobrem só ações, mas também pares de moedas e cripto. Ou seja, uma única integração resolve ao mesmo tempo as cotações e a taxa de câmbio de mercado — de mercado, que não é a taxa oficial de referência do banco central.

Uma particularidade prática dessas fontes: cada provedor tem seu próprio dialeto de JSON. A Alpha Vantage numera as chaves («1. open», «4. close»), o Yahoo Finance aninha os valores em arrays paralelos dentro de indicators, a Twelve Data devolve os preços como strings. A moral é sempre a mesma: mapeie os campos pelo nome e valide a resposta, em vez de confiar em uma ordem fixa (você verá isso nos exemplos).


3. APIs internacionais

Aqui não existe uma fonte livre «no estilo banco central»: todas exigem chave e aplicam limites, e o tempo real é quase sempre pago.

Serviço Cobertura Chave Limite gratuito Observações
Finnhub ações dos EUA e internacionais, FX, cripto necessária ~60 requisições/min tem WebSocket; histórico limitado no plano gratuito
Twelve Data ações, FX, cripto necessária ~800 requisições/dia OHLC, indicadores, REST limpo
Alpha Vantage 200 000+ tickers, 20+ bolsas necessária 25 requisições/dia (5/min) EOD e indicadores; tempo real dos EUA é pago
yfinance (não oficial, Yahoo) muito ampla dispensada sem limite explícito, mas é scraping para protótipos e aprendizado, não para produção
EODHD 150 000+ tickers globais necessária de teste forte em download massivo de histórico
Financial Modeling Prep preços + fundamentos necessária com limite demonstrações financeiras, múltiplos
Tiingo EOD + fundamentos dos EUA necessária com limite end-of-day limpo
Marketstack / Polygon.io global / tempo real EUA necessária de teste / pago na prática Polygon: baixa latência, ticks

Um detalhe recente importante: o IEX Cloud encerrou as atividades em 31 de agosto de 2024. Se algum guia ainda o recomenda, é informação desatualizada; os substitutos mais próximos são Alpha Vantage e Financial Modeling Prep.

Os planos gratuitos vão muito bem para um protótipo, mas esbarram rápido nos limites (as 25 requisições diárias da Alpha Vantage são, literalmente, umas duas dezenas de tickers por dia). Por isso, com um plano gratuito a lógica se constrói sempre em torno do cache e do armazenamento local: baixa-se o histórico uma vez e, depois, só se atualizam os pontos recentes.


4. Em que as cotações são mais difíceis que as taxas de câmbio

Várias diferenças que quebram um extrator ingênuo:

  • O ticker não é único. Um mesmo símbolo pode ser negociado em várias bolsas (a Petrobras é negociada na B3 como PETR4 e, via ADR, também em Nova York). Por isso a chave do instrumento é o par «bolsa + ticker», e ainda mais confiáveis são os identificadores internacionais ISIN ou FIGI.
  • Pregões e feriados. Cada bolsa tem seu horário, seu fuso horário e suas sessões de pré e pós-mercado. «O último preço» no fim de semana é o preço de sexta-feira.
  • Eventos corporativos. Um split de 1:10 «afunda» o preço 10 vezes — mas não é uma queda do mercado, e sim um recálculo técnico. É o análogo do campo nominal do artigo de moedas: ignore-o e obterá uma anomalia falsa. Dividendos e splits são corrigidos com o adjusted close.
  • A moeda do instrumento. O preço do papel é expresso na moeda da sua bolsa (USD, BRL, EUR...), e para uma carteira em uma única moeda é preciso convertê-lo — é justamente aí que entram as taxas de câmbio do artigo vizinho.
  • O volume (volume). É um número inteiro, mas enorme: milhões e bilhões de papéis. Precisa de um tipo inteiro de 64 bits.

5. Cinco soluções em linguagens diferentes

Os exemplos percorrem quatro provedores e formatos diferentes (a Alpha Vantage aparece duas vezes, em dois recortes: cotação instantânea e candles diários). Em todos, o foco está nas duas coisas do artigo de moedas: o preço é guardado em um tipo decimal (não float) e o volume, em um inteiro de 64 bits.

5.1. PHP — Alpha Vantage (GLOBAL_QUOTE, preços como strings)

php
<?php
declare(strict_types=1);

/**
 * Última cotação de uma ação via Alpha Vantage (GLOBAL_QUOTE).
 * Plano gratuito: 25 requisições/dia — o cache é obrigatório.
 */
function lastPrice(string $ticker): ?string
{
    $url = sprintf(
        'https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s',
        urlencode($ticker),
        getenv('ALPHAVANTAGE_KEY')
    );

    $raw = file_get_contents($url);
    if ($raw === false) {
        throw new RuntimeException('Não foi possível obter os dados da Alpha Vantage');
    }
    $json = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);

    // As chaves chegam numeradas: "01. symbol", "05. price"...
    $quote = $json['Global Quote'] ?? [];
    if (!$quote) {
        return null; // ticker desconhecido ou limite diário esgotado
    }

    // O preço chega como string: conservamos como está, sem passar por float
    return $quote['05. price'] ?? null;
}

echo 'IBM: '  . (lastPrice('IBM')  ?? 'sem dados') . " USD\n";
echo 'AAPL: ' . (lastPrice('AAPL') ?? 'sem dados') . " USD\n";

O ilustrativo aqui: a Alpha Vantage devolve os preços como strings JSON, então a precisão decimal sobrevive ao json_decode sem esforço — basta não convertê-la para float. Para a aritmética, como no artigo de moedas, BCMath ou uma biblioteca Money. Repare também que um ticker inexistente ou um limite esgotado não chegam como erro HTTP: a resposta vem vazia ou com uma nota de aviso, e é preciso tratar esse caso de forma explícita, em vez de confiar em um formato fixo.

5.2. Python — Finnhub (cotação atual, Decimal)

python
import os
import requests
from decimal import Decimal

FINNHUB_TOKEN = os.environ["FINNHUB_TOKEN"]  # chave gratuita, ~60 requisições/min


def finnhub_quote(symbol: str) -> dict[str, Decimal]:
    """Cotação atual de um símbolo (no plano gratuito, mercado dos EUA)."""
    resp = requests.get(
        "https://finnhub.io/api/v1/quote",
        params={"symbol": symbol, "token": FINNHUB_TOKEN},
        timeout=10,
    )
    resp.raise_for_status()
    d = resp.json()
    # Decimal(str(...)) fixa exatamente o valor que chegou no JSON
    return {
        "current":    Decimal(str(d["c"])),   # preço atual
        "open":       Decimal(str(d["o"])),
        "high":       Decimal(str(d["h"])),
        "low":        Decimal(str(d["l"])),
        "prev_close": Decimal(str(d["pc"])),
    }


if __name__ == "__main__":
    q = finnhub_quote("AAPL")
    print(f"AAPL: {q['current']} USD (abertura {q['open']}, máxima {q['high']})")

5.3. JavaScript / Node.js — Twelve Data (OHLC, volume)

javascript
// Grátis: ~800 requisições por dia. A chave é obrigatória.
const API_KEY = process.env.TWELVE_DATA_KEY;

async function twelveQuote(symbol) {
  const url = new URL("https://api.twelvedata.com/quote");
  url.searchParams.set("symbol", symbol);
  url.searchParams.set("apikey", API_KEY);

  const res = await fetch(url);
  const d = await res.json();
  if (d.status === "error") throw new Error(d.message);

  return {
    symbol: d.symbol,
    open: d.open,        // strings: não convertemos para Number sem necessidade
    high: d.high,
    low: d.low,
    close: d.close,
    volume: d.volume,    // o volume é um inteiro grande: string/BigInt
    exchange: d.exchange,
  };
}

twelveQuote("MSFT").then((q) =>
  console.log(`${q.symbol} (${q.exchange}): close ${q.close}, vol ${q.volume}`)
);

Em JavaScript não há tipo decimal, e Number é um double. Por isso os preços ficam como strings; para a aritmética de preços usa-se decimal.js/big.js e, para o volume, o BigInt nativo.

5.4. Go — Alpha Vantage (candles diários OHLCV)

go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "sort"
    "time"
)

// Alpha Vantage: grátis, 25 requisições/dia, 5/min.
type avDaily struct {
    Series map[string]struct {
        Open   string `json:"1. open"`
        High   string `json:"2. high"`
        Low    string `json:"3. low"`
        Close  string `json:"4. close"`
        Volume string `json:"5. volume"`
    } `json:"Time Series (Daily)"`
}

func dailyBars(symbol string) (avDaily, error) {
    key := os.Getenv("ALPHAVANTAGE_KEY")
    url := fmt.Sprintf(
        "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=%s&apikey=%s",
        symbol, key,
    )
    client := &http.Client{Timeout: 15 * time.Second}
    resp, err := client.Get(url)
    if err != nil {
        return avDaily{}, err
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var out avDaily
    if err := json.Unmarshal(body, &out); err != nil {
        return avDaily{}, err
    }
    return out, nil
}

func main() {
    bars, err := dailyBars("IBM")
    if err != nil {
        panic(err)
    }
    // localizamos o candle mais recente pela data
    dates := make([]string, 0, len(bars.Series))
    for d := range bars.Series {
        dates = append(dates, d)
    }
    sort.Strings(dates)
    last := dates[len(dates)-1]
    b := bars.Series[last]
    // os preços ficam como strings; para cálculos, shopspring/decimal
    fmt.Printf("IBM %s: O=%s H=%s L=%s C=%s V=%s\n",
        last, b.Open, b.High, b.Low, b.Close, b.Volume)
}

O conveniente é que a Alpha Vantage entrega os preços como strings: a precisão decimal se conserva «de fábrica»; basta não convertê-las para float64.

5.5. C# / .NET — Yahoo Finance (endpoint não oficial, decimal)

c#
using System.Text.Json;

// ⚠️ Endpoint não oficial do Yahoo Finance: o mesmo que a biblioteca yfinance usa.
// Serve para protótipos e aprendizado, mas sem garantias e NÃO para produção.
public static class YahooChart
{
    private static readonly HttpClient Http = new();

    public static async Task<(string Date, decimal Close)> LastDailyCloseAsync(string symbol)
    {
        var url = $"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=5d";

        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.UserAgent.ParseAdd("Mozilla/5.0"); // o Yahoo exige um User-Agent

        var resp = await Http.SendAsync(req);
        resp.EnsureSuccessStatusCode();
        using var doc = JsonDocument.Parse(await resp.Content.ReadAsStreamAsync());

        var result = doc.RootElement.GetProperty("chart").GetProperty("result")[0];
        var timestamps = result.GetProperty("timestamp");
        var closes = result.GetProperty("indicators")
            .GetProperty("quote")[0].GetProperty("close");

        var i = timestamps.GetArrayLength() - 1;
        var unix = timestamps[i].GetInt64();
        var close = closes[i].GetDecimal();    // decimal: correto para um preço
        var date = DateTimeOffset.FromUnixTimeSeconds(unix).ToString("yyyy-MM-dd");

        return (date, close);
    }
}

class Program
{
    static async Task Main()
    {
        var (date, close) = await YahooChart.LastDailyCloseAsync("AAPL");
        Console.WriteLine($"AAPL close {date}: {close} USD");
    }
}

Este exemplo mostra com honestidade a rota «de scraping» via Yahoo: não é preciso chave e os dados ficam perto do tempo real, mas o endpoint não é oficial e pode mudar sem aviso prévio — não dá para construir produção em cima dele. Os símbolos seguem a convenção do Yahoo: os papéis da B3 levam o sufixo .SA (PETR4.SA, VALE3.SA).


6. Em que tipo de dados armazenar as cotações

A regra básica é exatamente a mesma do artigo sobre taxas de câmbio: preços e grandezas monetárias, somente em um tipo decimal; nada de float/double. O ponto flutuante binário acumula erros de arredondamento e, em backtests de períodos longos, isso produz diferenças impossíveis de conciliar.

Campo Tipo Por quê
Preço (open/high/low/close, last) NUMERIC(18,6) / Decimal / decimal precisão sem perdas
Volume (volume) BIGINT / int64 bilhões de papéis não cabem em um int comum
Adjusted close NUMERIC(18,6) guardar junto do preço «cru», não no lugar dele
Momento da barra TIMESTAMPTZ sempre com o fuso horário da bolsa
Moeda do instrumento CHAR(3) (ISO 4217) para converter a carteira

Exemplo de tabela para candles (OHLCV):

sql
CREATE TABLE quotes (
    id           BIGSERIAL PRIMARY KEY,
    source       VARCHAR(16)   NOT NULL,   -- 'FINNHUB', 'AV', 'TWELVE', 'YAHOO'
    exchange     VARCHAR(16)   NOT NULL,   -- bolsa: 'NASDAQ', 'B3'...
    ticker       VARCHAR(20)   NOT NULL,   -- AAPL, PETR4...
    isin         CHAR(12),                 -- identificador confiável do instrumento
    ccy          CHAR(3)       NOT NULL,   -- moeda do preço: USD, BRL...
    ts           TIMESTAMPTZ   NOT NULL,   -- momento da barra/cotação
    interval     VARCHAR(8)    NOT NULL DEFAULT '1d', -- 1m | 1h | 1d
    open         NUMERIC(18,6) NOT NULL,
    high         NUMERIC(18,6) NOT NULL,
    low          NUMERIC(18,6) NOT NULL,
    close        NUMERIC(18,6) NOT NULL,
    adj_close    NUMERIC(18,6),            -- ajustado por splits/dividendos
    volume       BIGINT        NOT NULL DEFAULT 0,
    fetched_at   TIMESTAMPTZ   NOT NULL DEFAULT now(),
    UNIQUE (source, exchange, ticker, interval, ts)
);

Convém armazenar à parte os eventos corporativos (os splits com seu coeficiente e os dividendos com sua data), porque, quando eles aparecem, é preciso recalcular retroativamente o histórico de adj_close.


7. Dicas práticas

  • Distinga tempo real, atraso e EOD. Para a maioria das tarefas (carteira, análise, dashboard) bastam os dados com atraso ou end-of-day: são mais baratos e a licença deles é mais simples.
  • Respeite as licenças. É a grande diferença em relação às taxas dos bancos centrais. Os dados de bolsa em tempo real são regulados (as bolsas, a FINRA, a SEC), e até reexibi-los aos seus usuários pode exigir um acordo. Por isso, por exemplo, o tempo real dos EUA na Alpha Vantage é pago. Antes de publicar dados, revise as condições da fonte.
  • Construa tudo em torno do cache. Com um limite de 25 requisições por dia não há outro caminho: baixe o histórico uma vez e, a partir daí, só atualizações incrementais; o resto é servido do seu próprio banco.
  • Trate os splits como o «nominal». Um salto de preço de N vezes é quase sempre um evento corporativo, não um movimento do mercado. A verificação simples «variação frente ao dia anterior maior que X%» captura tanto splits quanto erros de extração.
  • Identifique o instrumento por bolsa + ticker (melhor ainda, por ISIN/FIGI). Um mesmo ticker vive em várias praças.
  • Lembre-se dos pregões e feriados. Uma resposta vazia no fim de semana é o normal; tome a última data disponível da resposta, não a solicitada.
  • Não construa produção sobre o scraping do Yahoo. Para um protótipo, perfeito; para um serviço que deve viver anos, use uma API com garantias.

8. Onde se aplica

  • Trackers de carteira — valor atual dos ativos, P&L, conversão para a moeda-base.
  • Trading algorítmico e bots — sinais e execução (aqui já são necessários tempo real e livro de ofertas).
  • Dashboards e BI — visualização do mercado, recortes setoriais.
  • Screeners e backtesting — filtragem de papéis e validação de estratégias sobre o histórico (OHLCV + adjusted).
  • Robo-advisors e fintechs — recomendações e gestão automatizada.
  • Contabilidade e avaliação — reavaliação de investimentos a preço de mercado em uma data.
  • Alertas — avisos quando o preço atinge um nível definido.

Em resumo

A extração de cotações da bolsa se parece tecnicamente com a de taxas de câmbio: os mesmos tipos decimais, o mesmo cache, a mesma cautela com os saltos «técnicos» (só que aqui o papel do nominal fica com os splits). Mas somam-se três coisas: o ticker vem atrelado a uma bolsa (melhor ainda, a um ISIN/FIGI), o histórico exige ajustes por eventos corporativos e — o mais importante na prática — os dados têm licença: de graça há quase só atraso e end-of-day, e o tempo real custa dinheiro e vem amarrado a condições. Para começar, bastam os planos gratuitos de Finnhub, Twelve Data ou Alpha Vantage, que ainda cobrem pares de moedas e se conectam assim ao tema do artigo vizinho sobre taxas de câmbio. Some a disciplina de tipos, o cache e o respeito às licenças, e terá cotações em que apoiar seus cálculos.