API gratuita de dados da Bolsa em Python
Consulte uma API gratuita de dados da Bolsa em Python com requests e pandas. Tutorial no Ubuntu que carrega JSON em um DataFrame e exibe o volume médio de 20 dias.
Chamar uma API gratuita de dados do mercado acionário a partir do Python exige uma única solicitação HTTP e duas bibliotecas que a maioria das pessoas já tem: requests para obter o JSON e pandas para convertê-lo em um DataFrame. Não é necessária uma chave de API. Este passo a passo é executado do início ao fim em um contêiner Ubuntu recém-criado e termina exibindo o volume médio de 20 sessões de um único ticker. O conteúdo disponibilizado pelos endpoints é explicado no guia sobre API gratuita de dados do mercado acionário; esta página apresenta a versão em Python da mesma API.
Configurando Python, requests e pandas em um contêiner Ubuntu novo
Uma imagem padrão do Ubuntu não inclui o módulo venv do Python, e as versões recentes se recusam a instalar pacotes no interpretador do sistema. Um ambiente virtual contorna os dois problemas e funciona da mesma forma em todas as versões atuais do Ubuntu. Execute estes comandos como root ou acrescente sudo às linhas com apt-get.
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y python3 python3-venv
python3 -m venv .venv
. .venv/bin/activate
pip install "requests>=2.31,<3" "pandas>=2.0,<4"
Os intervalos de versão são intencionais. Cada um aceita qualquer versão mantida da biblioteca e exclui apenas uma futura versão principal cujo comportamento ainda é desconhecido. Isso permite que a mesma linha continue instalando corretamente à medida que o Ubuntu atualiza sua versão padrão do Python. Nada está fixado em uma versão que por acaso exista hoje. Tudo abaixo pressupõe que o ambiente esteja ativo.
Como chamar uma API gratuita de dados do mercado acionário em Python?
Os endpoints de demonstração respondem a solicitações GET simples, sem chave nem cadastro. O primeiro script solicita uma das consultas selecionadas e imprime a estrutura do resultado.
import json
import requests
BASE = "https://ai.strasmore.com/api/demo"
resp = requests.get(BASE, params={"q": "dividend_yield_leaders"}, timeout=30)
resp.raise_for_status()
payload = resp.json()
print(list(payload.keys()))
print(payload["columns"])
print(json.dumps(payload["rows"][0], indent=2))
requests.get monta a string da consulta a partir de params, raise_for_status() transforma qualquer status 4xx ou 5xx em uma exceção, e .json() interpreta o corpo como um dicionário. Reduzido à sua estrutura, esse dicionário se parece com isto:
{
"key": "dividend_yield_leaders",
"label": "...",
"nl": "Which large-cap US stocks currently have the highest dividend yields?",
"sql": "SELECT ...",
"columns": ["as_of", "ticker", "dividend_yield_pct", "price", "market_cap_bn", "price_to_earnings"],
"rows": [{"as_of": "<date>", "ticker": "<symbol>", "dividend_yield_pct": <number>, ...}, ...],
"elapsed": "...",
"source": "Strasmore Research",
"more": "..."
}
Duas chaves são importantes para o pandas. columns nomeia os campos na ordem correta, e rows contém um objeto JSON por registro, com esses nomes como chaves. A chave sql traz a consulta exata que gerou os números e é devolvida em todas as respostas. Isso torna os dados auditáveis, em vez de uma caixa-preta. Uma solicitação GET para /api/demo/catalog lista todas as chaves selecionadas e o endpoint SQL usado em seguida.
Como carregar o JSON em um DataFrame do pandas?
As consultas selecionadas respondem a perguntas fixas. Os dados diários de um ticker à sua escolha vêm do endpoint SQL sem cadastro em /api/demo/sql. Ele recebe uma consulta somente de leitura no parâmetro sql e devolve o mesmo par columns e rows. Os limites, em setembro de 2026, aparecem em todas as respostas: 500 linhas, 20 segundos, um ano de histórico e nenhuma chave. A API SQL gratuita com uma chave eleva o limite para 100 consultas por dia e permite consultar um histórico mais longo. O código da solicitação é idêntico.
Duas salvaguardas mantêm a média consistente. Uma tabela atualizada durante a sessão pode conter mais de uma linha para o dia atual. Além disso, o volume desse dia é parcial enquanto o mercado está aberto. A consulta termina no dia anterior com date < today() e consolida eventuais linhas duplicadas de uma data com GROUP BY date e max(volume). Se você ainda não viu como uma barra diária é montada a partir do tape, como as barras OHLCV são construídas explica a origem dessas linhas.
import time
import requests
import pandas as pd
SQL_URL = "https://ai.strasmore.com/api/demo/sql"
TICKER = "AAPL"
SQL = f"""
SELECT date, max(volume) AS volume
FROM stocks_daily_aggs
WHERE ticker = '{TICKER}'
AND date < today()
GROUP BY date
ORDER BY date DESC
LIMIT 20
"""
def fetch(sql, attempts=4):
"""GET the SQL endpoint. Back off on 429 and 5xx; stop on any other 4xx."""
delay = 2.0
for attempt in range(1, attempts + 1):
resp = requests.get(SQL_URL, params={"sql": " ".join(sql.split())}, timeout=30)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429 or resp.status_code >= 500:
try:
wait = max(1.0, float(resp.headers.get("Retry-After")))
except (TypeError, ValueError):
wait = delay
print(f"HTTP {resp.status_code}; waiting {wait:.0f}s (attempt {attempt} of {attempts})")
time.sleep(wait)
delay *= 2
continue
raise SystemExit(f"HTTP {resp.status_code}: {resp.text[:400]}")
raise SystemExit("gave up after repeated 429 or 5xx responses")
payload = fetch(SQL)
df = pd.DataFrame(payload["rows"], columns=payload["columns"])
df["date"] = pd.to_datetime(df["date"])
df["volume"] = pd.to_numeric(df["volume"])
df = df.sort_values("date").reset_index(drop=True)
print(df.to_string(index=False))
avg_20 = df["volume"].mean()
first, last = df["date"].iloc[0], df["date"].iloc[-1]
print(f"{TICKER} 20-session average volume: {avg_20 / 1e6:.1f}M shares "
f"({first:%Y-%m-%d} to {last:%Y-%m-%d}, {len(df)} sessions)")
pd.DataFrame(rows, columns=columns) cria a tabela diretamente a partir da lista de objetos, e passar columns preserva a ordem dos campos da API. Em seguida, são feitas duas conversões: as datas chegam como strings e passam a ser timestamps reais, enquanto volume é convertido em número caso a resposta seja serializada como texto. Ordenar do mais antigo para o mais recente coloca o DataFrame na ordem esperada por um gráfico. df["volume"].mean() sobre exatamente twenty rows é a média de 20 sessões, e a última linha a imprime em milhões, junto do intervalo de datas abrangido.
O que o volume médio de 20 dias mede, na prática?
O volume médio suaviza uma série diária volátil. A quantidade de ações negociada em uma única sessão oscila com rebalanceamentos de índices, vencimentos de opções, datas de divulgação de resultados e manchetes. Vinte sessões correspondem a cerca de um mês do calendário. Esse período é longo o suficiente para atenuar esses picos e curto o suficiente para acompanhar uma mudança na intensidade de negociação de uma ação. O painel abaixo calcula a mesma estatística exibida pelo script, a partir da mesma tabela diária, ao longo dos últimos meses, colocando lado a lado as sessões brutas e a média móvel.
| sessão | volume em milhões | média de 20 dias em milhões |
|---|---|---|
| 2026-08-17 | 38.2 | 51.9 |
| 2026-08-18 | 53.4 | 52.5 |
| 2026-08-19 | 50.5 | 53 |
| 2026-08-20 | 41 | 53.1 |
| 2026-08-21 | 46.9 | 53 |
| 2026-08-24 | 34.7 | 52.3 |
| 2026-08-25 | 25.9 | 51 |
| 2026-08-26 | 34 | 49.9 |
| 2026-08-27 | 32.4 | 47.8 |
| 2026-08-28 | 38.6 | 43.1 |
| 2026-08-31 | 41.2 | 41.4 |
| 2026-09-01 | 53.2 | 40.6 |
| 2026-09-02 | 33.8 | 39.8 |
| 2026-09-03 | 37.2 | 39.4 |
| 2026-09-04 | 39.6 | 39.7 |
| 2026-09-08 | 35.5 | 39.2 |
| 2026-09-09 | 65.6 | 40.6 |
| 2026-09-10 | 70 | 42 |
| 2026-09-11 | 50.7 | 42.5 |
| 2026-09-14 | 39.3 | 43.1 |
O SQL exato por trás de cada número
SELECT
session,
volume_millions,
avg_20d_millions
FROM
(
SELECT
toString(d) AS session,
round(vol / 1e6, 1) AS volume_millions,
round(avg(vol) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) / 1e6, 1) AS avg_20d_millions,
count() OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS sessions_in_window
FROM
(
SELECT
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
AND date >= today() - 75
AND date < today()
GROUP BY date
)
)
WHERE sessions_in_window = 20
ORDER BY sessionA série diária tem um ponto por sessão. A série suavizada é a média dessa sessão e das dezenove sessões anteriores. O painel vai de 2026-08-17 a 2026-10-02, com 34 sessões que têm uma janela completa de vinte sessões anteriores. Na última dessas sessões, a AAPL movimentou 33.3 milhões de ações, contra um volume médio de 20 sessões de 42.2 milhões. Essa é a média que o script exibiu no dia em que esta página foi gerada. Se você executar o script hoje, as vinte sessões já terão avançado, e o número também terá mudado.
Trocando o ticker
Altere TICKER e o script funcionará para qualquer símbolo listado nos EUA presente na tabela. A comparação abaixo executa o mesmo cálculo de 20 sessões para quatro nomes conhecidos, oferecendo uma noção rápida da escala do que “volume médio” significa entre um ETF de índice e três ações de mega capitalização.
| ticker | média de 20 dias em milhões | primeira sessão | última sessão |
|---|---|---|---|
| NVDA | 110 | 2026-09-04 | 2026-10-02 |
| SPY | 46 | 2026-09-04 | 2026-10-02 |
| AAPL | 42.2 | 2026-09-04 | 2026-10-02 |
| MSFT | 21.3 | 2026-09-04 | 2026-10-02 |
O SQL exato por trás de cada número
SELECT
ticker,
round(avg(vol) / 1e6, 1) AS avg_20d_millions,
toString(min(d)) AS first_session,
toString(max(d)) AS last_session
FROM
(
SELECT
ticker,
d,
vol,
row_number() OVER (PARTITION BY ticker ORDER BY d DESC) AS rn
FROM
(
SELECT
ticker,
date AS d,
toFloat64(max(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'MSFT')
AND date >= today() - 45
AND date < today()
GROUP BY ticker, date
)
)
WHERE rn <= 20
GROUP BY ticker
HAVING count() = 20
ORDER BY avg_20d_millions DESCNVDA apresenta a maior média de 20 sessões entre os quatro, com 110 milhões de ações por sessão, enquanto MSFT registra a menor, com 21.3 milhões. Todas as médias são calculadas para as sessões entre 2026-09-04 e 2026-10-02. O valor de AAPL aqui é o mesmo número em que termina o traçado acima: uma única definição, calculada uma vez e reutilizada em todos os pontos em que aparece.
Como é um erro 429 e como repetir a solicitação de forma adequada?
O limite de requisições aparece como um código de status HTTP, e não como dados. A resposta traz o status 429 Too Many Requests e, muitas vezes, um cabeçalho Retry-After com o número de segundos de espera. O corpo não contém os dados solicitados. Por isso, o auxiliar fetch verifica status_code antes de analisar qualquer conteúdo. As regras são, nesta ordem:
200: analise e retorne o JSON.429ou qualquer5xx: aguardeRetry-Afterquando esse valor estiver presente. Caso contrário, aplique um intervalo progressivo de espera (2, 4, 8 e depois 16 segundos) e tente novamente, em um total de quatro tentativas.- Qualquer outro
4xx: pare. O corpo informa exatamente por que a consulta foi rejeitada: uma tabela inexistente ou uma instrução bloqueada pelo controle de acesso somente para leitura. Repetir a solicitação não muda o resultado. Por exemplo, uma tabela desconhecida retorna um400.
Dois hábitos ajudam a evitar o limite. Os resultados selecionados são atualizados no máximo a cada dez minutos. Um loop que consulta os dados com mais frequência baixa os mesmos bytes, e armazenar a resposta localmente não custa nada. Solicite apenas o que precisa: LIMIT 20 para uma média de 20 sessões, em vez de um ano inteiro de linhas que serão descartadas. Falhas de rede, como uma conexão interrompida ou uma instabilidade de DNS, aparecem como requests.RequestException. O auxiliar não captura esses erros. Envolva a chamada em try/except se o script for executado sem supervisão.
Perguntas frequentes
Existe uma API gratuita de dados do mercado acionário para Python?
Sim. Os endpoints de demonstração deste guia respondem a solicitações GET não autenticadas de requests com JSON que é carregado diretamente no pandas: consultas selecionadas em /api/demo?q=<key> e SQL somente leitura escrito manualmente em /api/demo/sql, limitados a 500 linhas e um ano de histórico. Sem chave e sem cadastro.
Preciso de uma chave de API para obter dados de ações em Python?
Não para o nível de demonstração usado aqui. Uma chave gratuita eleva o limite para 100 consultas por dia e oferece um histórico mais longo, sem alterar o código Python. Desenvolvedores que transformam os mesmos endpoints em ferramentas para um LLM podem começar por habilidades de dados de mercado para agentes de IA.
Como converto uma resposta JSON de uma API em um DataFrame do pandas?
Analise o corpo com resp.json() e passe a lista de objetos de linhas para pd.DataFrame(rows, columns=columns). Converta strings de data com pd.to_datetime e strings numéricas com pd.to_numeric antes de fazer cálculos. O JSON não tem um tipo de data, e algumas APIs serializam decimais como texto.
O que significa um erro 429 ao chamar uma API de ações?
O HTTP 429 significa Too Many Requests: o servidor está limitando a taxa de solicitações do cliente. Aguarde o número de segundos indicado no cabeçalho Retry-After, quando ele for enviado. Caso contrário, aumente progressivamente o intervalo entre as tentativas e armazene as respostas em cache, em vez de consultar repetidamente um resultado que só é atualizado a cada dez minutos.
Como calcular o volume médio no pandas?
Carregue uma linha por sessão com uma coluna volume, mantenha as últimas vinte sessões completas e chame df["volume"].mean(). Para uma versão móvel sobre um histórico mais longo, use df["volume"].rolling(20).mean(), que é o que o painel de rastreamento acima apresenta.
Cada painel acima inclui o SQL exato logo abaixo, e o script segue a mesma lógica, com uma chamada requests no início. Chave de API gratuita. 100 consultas por dia e 22 anos de histórico, com SQL sobre os negócios brutos. Sem cartão. Obtenha uma chave de API