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
nominaldo 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
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)
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)
// 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)
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)
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):
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.