
Yahoo Finance
- 213 installs
- 173 repo stars
- Updated June 14, 2026
- gauss314/skills
For development and infrastructure management.
About
yahoo-finance is an AI coding tool that enhances development workflows. Builders use it for infrastructure, integration, and platform development within the catalog ecosystem.
- yahoo-finance
- Development
Yahoo Finance by the numbers
- 213 all-time installs (skills.sh)
- Ranked #1,842 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gauss314/skills --skill yahoo-financeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 213 |
|---|---|
| repo stars | ★ 173 |
| Last updated | June 14, 2026 |
| Repository | gauss314/skills ↗ |
What it does
For development and infrastructure management.
Files
Yahoo Finance — API Directa (sin yfinance)
API no oficial de Yahoo Finance. Accedé a datos de acciones, ETFs, crypto, forex, bonos, índices, opciones, fundamentos y noticias mediante requests HTTP directos sin usar yfinance ni ningún wrapper.
Base URL: https://query1.finance.yahoo.com Alternativa: https://query2.finance.yahoo.com
---
⚠️ Importante
- Yahoo no tiene API pública oficial desde 2017. Estos endpoints son no oficiales y pueden cambiar sin aviso.
- Algunos endpoints requieren autenticación via cookie + crumb (abajo se explica).
- El endpoint
v8/finance/chartfunciona sin autenticación (solo User-Agent de navegador). - Siempre implementar rate limiting y manejo de errores.
---
Documentación completa
Para referencia exhaustiva de todos los endpoints, campos, JSON structures, códigos de error, tickers internacionales, estrategias de rate limiting y ejemplos detallados, ver:
📖 [references/API_REFERENCE.md](./references/API_REFERENCE.md)
Ese documento incluye:
- Los 33 módulos de
quoteSummarycon cada campo documentado - JSON responses completas de cada endpoint
- Tickers internacionales (
.BApara Argentina,.SApara Brasil, etc.) - WebSocket streaming
- Screener, lookup, trending
- Estrategias de rate limiting con exponential backoff y rotación de User-Agent
---
Autenticación: Cookie + Crumb
Varios endpoints requieren un crumb (token CSRF) que se obtiene con cookies de sesión.
import requests
BASE = "https://query1.finance.yahoo.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
def yahoo_session():
"""Retorna requests.Session con cookie A3 y crumb."""
s = requests.Session()
s.headers.update(HEADERS)
s.get("https://fc.yahoo.com", timeout=10)
crumb = s.get(f"{BASE}/v1/test/getcrumb", timeout=10).text.strip()
s.params = {"crumb": crumb}
return s
# Uso:
# s = yahoo_session()
# r = s.get("https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL,MSFT")Endpoints según autenticación
| Sin auth (solo User-Agent) | Requieren crumb |
|---|---|
v8/finance/chart (históricos) | v7/finance/quote (precios) |
v1/finance/search (búsqueda) | v10/finance/quoteSummary (fundamentos) |
v1/finance/trending (tendencias) | v7/finance/options (opciones) |
v1/finance/lookup (lookup) | v6/finance/recommendationsbysymbol |
---
Endpoints — Resumen rápido
1. Históricos OHLCV — v8/finance/chart
GET https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?range=1y&interval=1d
| Parámetro | Ejemplos |
|---|---|
range | 1d, 5d, 1mo, 3mo, 6mo, 1y, 5y, 10y, ytd, max |
interval | 1m, 5m, 15m, 1h, 1d, 1wk, 1mo |
events | div,splits (incluye dividendos y splits) |
import requests
headers = {"User-Agent": "Mozilla/5.0 (...) Chrome/120.0.0.0 Safari/537.36"}
r = requests.get("https://query1.finance.yahoo.com/v8/finance/chart/AAPL",
params={"range": "1y", "interval": "1d", "events": "div,splits"},
headers=headers)
data = r.json()["chart"]["result"][0]
# data["timestamp"] -> fechas Unix
# data["indicators"]["quote"][0] -> open, high, low, close, volume
# data["indicators"]["adjclose"][0] -> precios ajustados
# data["events"] -> dividendos y splits2. Quote — v7/finance/quote
s = yahoo_session()
r = s.get("https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL,MSFT,GOOGL")
quotes = r.json()["quoteResponse"]["result"]
for q in quotes:
print(q["symbol"], q["regularMarketPrice"], q["regularMarketChangePercent"])Campos: regularMarketPrice, regularMarketChangePercent, marketCap, trailingPE, fiftyTwoWeekHigh/Low, dividendYield, volume, marketState (PRE/REGULAR/POST/CLOSED).
3. Fundamentos — v10/finance/quoteSummary
s = yahoo_session()
r = s.get("https://query1.finance.yahoo.com/v10/finance/quoteSummary/AAPL",
params={"modules": "assetProfile,financialData,defaultKeyStatistics,incomeStatementHistory,balanceSheetHistory,cashflowStatementHistory,earnings,recommendationTrend"})
fundamentals = r.json()["quoteSummary"]["result"][0]
profile = fundamentals["assetProfile"] # sector, industry, employees, description
financials = fundamentals["financialData"] # EBITDA, revenue, margins, ROE, ROA
stats = fundamentals["defaultKeyStatistics"] # beta, shares, PE, PEG, short infoHay 33 módulos disponibles. Ver lista completa en API_REFERENCE.md sección 4.
4. Opciones — v7/finance/options
s = yahoo_session()
r = s.get("https://query1.finance.yahoo.com/v7/finance/options/AAPL")
data = r.json()["optionChain"]["result"][0]
expirations = data["expirationDates"]
strikes = data["strikes"]
options = data["options"][0] # calls + puts5. Búsqueda + Noticias — v1/finance/search
r = requests.get("https://query1.finance.yahoo.com/v1/finance/search",
params={"q": "Apple", "quotesCount": 3, "newsCount": 5},
headers=HEADERS)
data = r.json()
# data["quotes"] -> tickers encontrados
# data["news"] -> noticias relacionadas---
Scripts
| Script | Descripción |
|---|---|
| [batch_fetch.py](./scripts/batch_fetch.py) | Batch multi-ticker con token bucket + ThreadPoolExecutor. Quote batching (todos en 1 request). Charts en paralelo respetando rate limit. |
| [fetch_all.py](./scripts/fetch_all.py) | Script integral: fetch de histórico + quote + fundamentals + opciones + search + recomendaciones. Genérico para cualquier ticker con argumentos CLI. |
| [fetch_quote.py](./scripts/fetch_quote.py) | Fetch rápido de quote + fundamentals por ticker. |
| [download_historical.py](./scripts/download_historical.py) | Descarga históricos OHLCV a CSV. |
batch_fetch.py — Batch multi-ticker con rate limiting
# Test seguro (3 tickers, solo chart, 2 req/s)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA --chart --rate 2.0
# Batch completo (quote en 1 request + charts en paralelo)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA,GOOGL,META,AMZN,TSLA --all --rate 2.0 --workers 4
# Solo quotes (siempre 1 request, imposible rate-limit)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA,GOOGL --quote
# Personalizar rate + workers
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA --all --rate 1.5 --workers 3 --range 5yFlags clave:
--rate: tokens/segundo del token bucket (default: 2.0 — máximo seguro)--burst: tokens acumulables para ráfagas (default: 5)--workers: hilos en paralelo (default: 4)
Arquitectura: 1. Fase 1 — Quote batch: todos los tickers en 1 sola request (v7/finance/quote?symbols=AAPL,MSFT,...) 2. Fase 2 — Token bucket: ThreadPoolExecutor con TokenBucket thread-safe compartido entre workers. Cada worker adquiere un token antes de cada request. A 2 req/s con burst=5, se pueden lanzar hasta 5 requests instantáneas sin bloquear; luego el bucket estabiliza el throughput.
fetch_all.py — El script principal
python scripts/fetch_all.py --ticker AAPL --all # Todo lo disponible
python scripts/fetch_all.py --ticker GGAL --all --range 5y --interval 1d # GGAL con 5 años
python scripts/fetch_all.py --ticker MSFT --chart --range max # Histórico completo
python scripts/fetch_all.py --ticker NVDA --quote --fundamentals # Quote + fundamentals
python scripts/fetch_all.py --ticker TSLA --all --all-modules # Todos los módulos (~33)
python scripts/fetch_all.py --ticker AAPL --options # Solo opciones
python scripts/fetch_all.py -t AAPL -o mi_data.json -q # Output a archivo, quietFlags principales:
--all: fetch de todo (chart, quote, fundamentals, options, search, recommendations)--chart: histórico OHLCV--quote: precio en tiempo real--fundamentals: fundamentos (quoteSummary)--options: cadena de opciones--search: búsqueda y noticias--range/--interval: control del histórico--modules/--all-modules: módulos de quoteSummary--output: archivo JSON de salida
Prueba real con GGAL (testeado)
>> Chart (1y, 1d)... OK 250 bars
>> Quote... OK Price: $50.33 (-1.62%)
>> Fundamentals... OK 12 módulos (core)
>> Options... OK 5 expiration dates, 17 calls, 19 puts
>> Search+News... OK 5 news items
Guardado en: ggal_temp.json (123 KB)---
Tickers internacionales
Yahoo Finance usa sufijos para mercados fuera de EE.UU.:
| Mercado | Sufijo | Ejemplo |
|---|---|---|
| Argentina (BCBA) | .BA | GGAL.BA, YPFD.BA |
| Brasil (Bovespa) | .SA | PETR4.SA, VALE3.SA |
| México (BMV) | .MX | WALMEX.MX |
| Crypto | -USD | BTC-USD, ETH-USD |
| Forex | =X | EURUSD=X |
| Índices | ^ | ^GSPC (S&P 500) |
Ver lista completa en API_REFERENCE.md sección 14.
---
Rate Limits
| Límite | Comportamiento |
|---|---|
| ~2 req/s | Seguro |
| 3-5 req/s | Probabilidad alta de 429 |
| >10 req/s | IP block temporal |
Siempre usar `time.sleep(0.5)` entre requests e implementar exponential backoff.
Para fetch multi-ticker, usar `batch_fetch.py` que implementa token bucket a 2 req/s con burst de 5. A diferencia de yfinance (que no tiene rate limiter y lanza N threads simultáneos), batch_fetch garantiza que el throughput agregado no supere el límite seguro.
---
Errores Comunes
| Error | Causa | Solución |
|---|---|---|
401 Unauthorized | Falta crumb | Usar yahoo_session() |
429 Too Many Requests | Rate limit | Esperar 30-60s |
Bad Request | Crumb expirado | Regenerar crumb |
result vacío | Ticker inválido | Verificar con search primero |
Python-requests block | User-Agent default | Setear uno de navegador |
---
Estructura del skill
skills/yahoo-finance/
├── SKILL.md # Este archivo (quickstart)
├── references/
│ └── API_REFERENCE.md # Documentación completa de todos los endpoints
└── scripts/
├── batch_fetch.py # Batch multi-ticker con rate limiting (recomendado)
├── fetch_all.py # Script integral
├── fetch_quote.py # Quote + fundamentals rápido
└── download_historical.py # Históricos a CSVYahoo Finance API Reference Completa
Documentación exhaustiva de los endpoints no oficiales de Yahoo Finance.
Actualizada a Junio 2026 — basada en ingeniería inversa de yfinance y testing directo.---
Índice
1. Autenticación: Cookie + Crumb 2. v8/finance/chart — Históricos OHLCV 3. v7/finance/quote — Precio en tiempo real 4. v10/finance/quoteSummary — Fundamentos 5. v7/finance/options — Cadena de opciones 6. v1/finance/search — Búsqueda y noticias 7. v6/finance/recommendationsbysymbol — Recomendaciones 8. v1/finance/trending — Trending symbols 9. v1/finance/lookup — Lookup de tickers 10. v1/finance/screener — Screener 11. WebSocket streaming 12. Rate Limiting y Estrategias 13. Códigos de Error y Troubleshooting 14. Tickers Internacionales 15. Campos Comunes entre Endpoints
---
1. Autenticación: Cookie + Crumb
Yahoo usa un sistema cookie + crumb para proteger ciertos endpoints contra bots. No es OAuth ni requiere API key — es un CSRF token casero.
Flujo completo
Cliente Yahoo
| |
| GET https://fc.yahoo.com |
|-------------------------------->|
| Set-Cookie: A3=XXXXXXXXX... |
|<--------------------------------|
| |
| GET /v1/test/getcrumb |
| (con cookie A3) |
|-------------------------------->|
| crumb: "abcdef123456" |
|<--------------------------------|
| |
| GET /v7/finance/quote |
| ?crumb=abcdef123456 |
|-------------------------------->|
| JSON con datos |
|<--------------------------------|Implementación en Python
import requests
BASE = "https://query1.finance.yahoo.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
def yahoo_session():
s = requests.Session()
s.headers.update(HEADERS)
s.get("https://fc.yahoo.com", timeout=10) # paso 1: obtener cookie A3
crumb = s.get(f"{BASE}/v1/test/getcrumb", timeout=10).text.strip() # paso 2: obtener crumb
s.params = {"crumb": crumb} # paso 3: adjuntar crumb a todas las requests
return sEndpoints que requieren crumb
| Endpoint | Requiere crumb |
|---|---|
v8/finance/chart | ❌ No |
v7/finance/quote | ✅ Sí |
v10/finance/quoteSummary | ✅ Sí |
v7/finance/options | ✅ Sí |
v1/finance/search | ❌ No |
v6/finance/recommendationsbysymbol | ✅ Sí |
v1/finance/trending | ❌ No |
v1/finance/lookup | ❌ No |
v1/finance/screener | ✅ Sí (a veces) |
El crumb expira
- El crumb tiene validez de ~unos minutos a varias horas.
- No hay un TTL documentado. Si recibís
{"finance":{"error":{"code":"Bad Request"}}}, hay que regenerar el crumb. - Estrategia segura: crear una nueva sesión por cada request que requiera crumb, o cachear y reintentar si falla.
---
2. v8/finance/chart — Históricos OHLCV
Endpoint
GET https://query1.finance.yahoo.com/v8/finance/chart/{symbol}Parámetros
| Parámetro | Valores | Obligatorio | Descripción |
|---|---|---|---|
range | 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max | No* | Período de tiempo |
interval | 1m, 2m, 5m, 15m, 30m, 60m, 1h, 1d, 1wk, 1mo | Sí | Frecuencia de los datos |
period1 | Unix timestamp | No* | Fecha de inicio (alternativa a range) |
period2 | Unix timestamp | No* | Fecha de fin (default: now) |
events | div, splits, div,splits | No | Incluir dividendos y/o splits |
includePrePost | true, false | No | Incluir datos pre/post market (intraday) |
\* Usar range o period1/period2, no ambos.
Combinaciones range/interval válidas
Típicamente Yahoo limita qué intervalos podés usar según el rango:
| Range | Intervales válidos |
|---|---|
1d | 1m, 2m, 5m |
5d | 1m, 2m, 5m, 15m, 30m |
1mo | 1m, 5m, 15m, 30m, 60m, 1h, 1d |
3mo | 1d, 1wk |
6mo | 1d, 1wk |
1y | 1d, 1wk |
2y | 1d, 1wk, 1mo |
5y | 1d, 1wk, 1mo |
max | 1d, 1wk, 1mo |
Nota: Intraday (1m, 5m) solo retiene 7-60 días de datos.
Respuesta JSON
{
"chart": {
"result": [
{
"meta": {
"currency": "USD",
"symbol": "AAPL",
"exchangeName": "NMS",
"instrumentType": "EQUITY",
"firstTradeDate": 345479400,
"regularMarketTime": 1717439040,
"regularMarketPrice": 196.89,
"regularMarketOpen": 195.19,
"regularMarketDayHigh": 197.92,
"regularMarketDayLow": 194.81,
"regularMarketVolume": 45200000,
"regularMarketPreviousClose": 194.50,
"gmtoffset": -14400,
"timezone": "EDT",
"exchangeTimezoneName": "America/New_York",
"chartPreviousClose": 194.50,
"previousClose": 194.50,
"scale": 3,
"priceHint": 2,
"currentTradingPeriod": {
"pre": {
"timezone": "EDT",
"start": 1717401600,
"end": 1717421400,
"gmtoffset": -14400
},
"regular": {
"timezone": "EDT",
"start": 1717421400,
"end": 1717444800,
"gmtoffset": -14400
},
"post": {
"timezone": "EDT",
"start": 1717444800,
"end": 1717459200,
"gmtoffset": -14400
}
},
"dataGranularity": "1d",
"range": "1mo",
"validRanges": ["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]
},
"timestamp": [1715904000, 1715990400, 1716076800, ...],
"indicators": {
"quote": [
{
"open": [189.43, 187.70, 189.02, ...],
"high": [190.68, 188.70, 190.00, ...],
"low": [187.88, 186.80, 188.33, ...],
"close": [189.66, 188.27, 189.83, ...],
"volume": [34600800, 30563400, 29645200, ...]
}
],
"adjclose": [
{
"adjclose": [189.56, 188.17, 189.73, ...]
}
]
},
"events": {
"dividends": {
"1718323200": {
"amount": 0.25,
"date": 1718323200
}
},
"splits": {
"1598572800": {
"date": 1598572800,
"numerator": 4,
"denominator": 1,
"splitRatio": "4:1"
}
}
}
}
],
"error": null
}
}Cómo parsear
r = requests.get("https://query1.finance.yahoo.com/v8/finance/chart/AAPL",
params={"range": "1y", "interval": "1d", "events": "div,splits"},
headers=HEADERS)
data = r.json()
result = data["chart"]["result"][0]
# Timestamps
timestamps = result["timestamp"]
# OHLCV como arrays paralelos
opens = result["indicators"]["quote"][0]["open"]
highs = result["indicators"]["quote"][0]["high"]
lows = result["indicators"]["quote"][0]["low"]
closes = result["indicators"]["quote"][0]["close"]
volumes = result["indicators"]["quote"][0]["volume"]
# Precios ajustados
adj_closes = result["indicators"]["adjclose"][0]["adjclose"]
# Meta
meta = result["meta"]
print(meta["symbol"], meta["currency"], meta["regularMarketPrice"])
# Eventos
events = result.get("events", {})
dividends = events.get("dividends", {})
splits = events.get("splits", {})Arrays paralelos
Los datos vienen como arrays paralelos indexados por timestamp. Para convertirlos a filas:
rows = []
for i in range(len(timestamps)):
rows.append({
"date": datetime.fromtimestamp(timestamps[i], tz=timezone.utc),
"open": opens[i],
"high": highs[i],
"low": lows[i],
"close": closes[i],
"volume": volumes[i],
"adjclose": adj_closes[i],
})Dividendos y splits
Los dividendos y splits vienen en un formato diferente (mapeados por timestamp como string):
for ts_str, div in dividends.items():
print(f"Divi: ${div['amount']} en {datetime.fromtimestamp(int(ts_str))}")
for ts_str, split in splits.items():
print(f"Split: {split['numerator']}:{split['denominator']} "
f"en {datetime.fromtimestamp(int(ts_str))}")Notas importantes sobre v8/chart
- Es el endpoint más estable de Yahoo Finance. Funciona sin autenticación.
- User-Agent es obligatorio. Sin un User-Agent de navegador, Yahoo devuelve error o datos vacíos.
- No usar con `yfinance` — este skill usa requests directas.
- Los
nullaparecen cuando no hay trading (fines de semana, feriados). adjclosees crucial para backtesting porque ajusta por splits y dividendos.
---
3. v7/finance/quote — Precio en tiempo real
Endpoint
GET https://query1.finance.yahoo.com/v7/finance/quote?symbols={symbol1},{symbol2},...Requiere crumb (ver sección Autenticación).
Parámetros
| Parámetro | Descripción |
|---|---|
symbols | Ticker(s) separados por coma (ej: AAPL,MSFT,GOOGL) |
crumb | Token de autenticación (se pasa automático con yahoo_session()) |
Respuesta JSON
{
"quoteResponse": {
"result": [
{
"language": "en-US",
"region": "US",
"quoteType": "EQUITY",
"typeDisp": "Equity",
"quoteSourceName": "Nasdaq Real Time Price",
"triggerable": true,
"customPriceAlertConfidence": "HIGH",
"currency": "USD",
"exchange": "NMS",
"shortName": "Apple Inc.",
"longName": "Apple Inc.",
"messageBoardId": "finmb_24937",
"exchangeTimezoneName": "America/New_York",
"exchangeTimezoneShortName": "EDT",
"gmtOffSetMilliseconds": -14400000,
"market": "us_market",
"marketState": "REGULAR",
"esgPopulated": true,
"firstTradeDateMilliseconds": 345479400000,
"priceHint": 2,
"regularMarketChange": {
"raw": 2.39,
"fmt": "2.39"
},
"regularMarketChangePercent": {
"raw": 1.2284,
"fmt": "1.23%"
},
"regularMarketPrice": {
"raw": 196.89,
"fmt": "196.89"
},
"regularMarketDayHigh": {
"raw": 197.92,
"fmt": "197.92"
},
"regularMarketDayLow": {
"raw": 194.81,
"fmt": "194.81"
},
"regularMarketVolume": {
"raw": 45200000,
"fmt": "45.2M"
},
"regularMarketPreviousClose": {
"raw": 194.50,
"fmt": "194.50"
},
"regularMarketOpen": {
"raw": 195.19,
"fmt": "195.19"
},
"averageDailyVolume3Month": {
"raw": 50300000,
"fmt": "50.3M"
},
"averageDailyVolume10Day": {
"raw": 42100000,
"fmt": "42.1M"
},
"fiftyTwoWeekLowChange": {
"raw": 55.68,
"fmt": "55.68"
},
"fiftyTwoWeekLowChangePercent": {
"raw": 0.3943,
"fmt": "39.43%"
},
"fiftyTwoWeekRange": {
"raw": "141.21 - 199.62",
"fmt": "141.21 - 199.62"
},
"fiftyTwoWeekHighChange": {
"raw": -2.73,
"fmt": "-2.73"
},
"fiftyTwoWeekHighChangePercent": {
"raw": -0.0137,
"fmt": "-1.37%"
},
"fiftyTwoWeekLow": {
"raw": 141.21,
"fmt": "141.21"
},
"fiftyTwoWeekHigh": {
"raw": 199.62,
"fmt": "199.62"
},
"dividendDate": 1718323200,
"earningsTimestamp": 1717459200,
"earningsTimestampStart": 1717459200,
"earningsTimestampEnd": 1717459200,
"earningsCallTimestampStart": 1717466400,
"earningsCallTimestampEnd": 1717466400,
"isEarningsDateEstimate": false,
"trailingAnnualDividendRate": {
"raw": 1.0,
"fmt": "1.00"
},
"trailingPE": {
"raw": 29.86,
"fmt": "29.86"
},
"trailingAnnualDividendYield": {
"raw": 0.0051,
"fmt": "0.51%"
},
"marketCap": {
"raw": 3020000000000,
"fmt": "3.02T"
},
"tradeable": false
}
],
"error": null
}
}Campos clave del quote
| Campo Ruta | Tipo | Descripción |
|---|---|---|
regularMarketPrice.raw | float | Precio actual |
regularMarketChangePercent.raw | float | Cambio % (ej: 1.23 = +1.23%) |
regularMarketVolume.raw | int | Volumen del día |
regularMarketOpen.raw | float | Apertura |
regularMarketDayHigh.raw | float | Máximo del día |
regularMarketDayLow.raw | float | Mínimo del día |
regularMarketPreviousClose.raw | float | Cierre anterior |
fiftyTwoWeekHigh.raw | float | Máximo 52 semanas |
fiftyTwoWeekLow.raw | float | Mínimo 52 semanas |
marketCap.raw | int | Capitalización bursátil |
trailingPE.raw | float | P/E ratio trailing |
trailingAnnualDividendYield.raw | float | Dividend yield |
trailingAnnualDividendRate.raw | float | Dividendo anual |
dividendDate | int | Próximo dividendo (Unix timestamp) |
earningsTimestamp | int | Próximo earnings (Unix timestamp) |
shortName | string | Nombre corto |
longName | string | Nombre largo |
exchange | string | Exchange (NMS, NYQ, NASDAQ, etc.) |
marketState | string | PRE, REGULAR, POST, CLOSED |
currency | string | Moneda (USD, ARS, etc.) |
averageDailyVolume3Month.raw | int | Volumen promedio 3 meses |
Notas
- Los campos con
rawyfmtson consistentes en todos los endpoints:rawes el valor numérico,fmtes el string formateado para mostrar. marketStatees útil para saber si el mercado está abierto.esgPopulatedindica si hay datos ESG disponibles.
---
4. v10/finance/quoteSummary — Fundamentos
Endpoint
GET https://query1.finance.yahoo.com/v10/finance/quoteSummary/{symbol}?modules={mod1},{mod2}Requiere crumb.
Módulos disponibles (33 total)
| # | Módulo | Descripción | Tamaño típico |
|---|---|---|---|
| 1 | assetProfile | Perfil completo: sector, industria, empleados, descripción, direcciones | Grande |
| 2 | summaryProfile | Resumen del perfil (versión corta) | Pequeño |
| 3 | financialData | Métricas financieras: EBITDA, revenue, profit margins, ROE, ROA, debt/equity | Mediano |
| 4 | defaultKeyStatistics | Estadísticas: beta, market cap, shares outstanding, float, short ratio | Mediano |
| 5 | incomeStatementHistory | Estado de resultados (varios años) | Grande |
| 6 | incomeStatementHistoryQuarterly | Estado de resultados trimestral | Grande |
| 7 | balanceSheetHistory | Balance general (varios años) | Grande |
| 8 | balanceSheetHistoryQuarterly | Balance general trimestral | Grande |
| 9 | cashflowStatementHistory | Flujo de caja (varios años) | Grande |
| 10 | cashflowStatementHistoryQuarterly | Flujo de caja trimestral | Grande |
| 11 | earnings | Ganancias históricas por trimestre | Mediano |
| 12 | earningsHistory | EPS reportado vs estimado por trimestre | Mediano |
| 13 | earningsTrend | Estimados de EPS futuros | Mediano |
| 14 | recommendationTrend | Recomendaciones: strong buy, buy, hold, sell por período | Mediano |
| 15 | upgradeDowngradeHistory | Historia de cambios de recomendación | Mediano |
| 16 | insiderTransactions | Transacciones de insider (compra/venta) | Mediano |
| 17 | insiderHolders | Tenedores insider y sus participaciones | Pequeño |
| 18 | institutionOwnership | Tenencia de instituciones, cambios, % | Mediano |
| 19 | fundOwnership | Tenencia de fondos mutuos | Mediano |
| 20 | majorDirectHolders | Mayores tenedores directos | Pequeño |
| 21 | majorHoldersBreakdown | % institutional, insider, público, otros | Pequeño |
| 22 | secFilings | Últimos SEC filings (10-K, 10-Q, 8-K) | Mediano |
| 23 | calendarEvents | Próximos earnings date, dividend date, ex-date | Pequeño |
| 24 | price | Información detallada de precio, pre/post market, 52w | Mediano |
| 25 | quoteType | Tipo: EQUITY, ETF, MUTUALFUND, INDEX, etc. | Pequeño |
| 26 | summaryDetail | Bid, ask, volume, avg volume, yield, beta | Mediano |
| 27 | symbol | Símbolo del ticker | Mínimo |
| 28 | topHoldings | Top holdings (para ETFs) | Grande (solo ETFs) |
| 29 | fundProfile | Perfil del fondo (para ETFs/Mutual Funds) | Grande (solo fondos) |
| 30 | indexTrend | Tendencia del índice | Pequeño |
| 31 | sectorTrend | Tendencia del sector | Pequeño |
| 32 | industryTrend | Tendencia de la industria | Pequeño |
| 33 | netSharePurchaseActivity | Actividad neta de recompra de acciones | Mediano |
Módulos core recomendados
Para un fetch rápido pero completo de cualquier equity:
assetProfile,financialData,defaultKeyStatistics,
incomeStatementHistory,balanceSheetHistory,cashflowStatementHistory,
earnings,earningsTrend,recommendationTrend,
calendarEvents,price,summaryDetailEjemplo de respuesta (assetProfile)
{
"quoteSummary": {
"result": [
{
"assetProfile": {
"address1": "One Apple Park Way",
"city": "Cupertino",
"state": "CA",
"zip": "95014",
"country": "United States",
"phone": "14089961010",
"website": "https://www.apple.com",
"industry": "Consumer Electronics",
"industryKey": "consumer-electronics",
"industryDisp": "Consumer Electronics",
"sector": "Technology",
"sectorKey": "technology",
"sectorDisp": "Technology",
"longBusinessSummary": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide...",
"fullTimeEmployees": 161000,
"companyOfficers": [
{
"name": "Mr. Timothy D. Cook",
"age": 63,
"title": "CEO & Director",
"yearBorn": 1961,
"fiscalYear": 2023,
"totalPay": {"raw": 63200000, "fmt": "63.2M"}
}
],
"auditRisk": 7,
"boardRisk": 3,
"compensationRisk": 6,
"shareHolderRightsRisk": 2,
"overallRisk": 5,
"governanceEpochDate": 1719792000,
"compensationAsOfEpochDate": 1704067200,
"maxAge": 1
}
}
],
"error": null
}
}financialData
{
"financialData": {
"currentPrice": {"raw": 196.89, "fmt": "196.89"},
"targetHighPrice": {"raw": 250.00, "fmt": "250.00"},
"targetLowPrice": {"raw": 150.00, "fmt": "150.00"},
"targetMeanPrice": {"raw": 205.43, "fmt": "205.43"},
"targetMedianPrice": {"raw": 205.00, "fmt": "205.00"},
"recommendationMean": {"raw": 1.8, "fmt": "1.80"},
"recommendationKey": "buy",
"numberOfAnalystOpinions": {"raw": 42, "fmt": "42"},
"totalRevenue": {"raw": 391400000000, "fmt": "391.4B"},
"revenuePerShare": {"raw": 24.95, "fmt": "24.95"},
"revenueGrowth": {"raw": 0.071, "fmt": "7.1%"},
"grossProfits": {"raw": 170800000000, "fmt": "170.8B"},
"grossMargin": {"raw": 0.452, "fmt": "45.2%"},
"ebitda": {"raw": 130000000000, "fmt": "130B"},
"ebitdaMargins": {"raw": 0.332, "fmt": "33.2%"},
"operatingMargin": {"raw": 0.293, "fmt": "29.3%"},
"profitMargins": {"raw": 0.251, "fmt": "25.1%"},
"netIncomeToCommon": {"raw": 96990000000, "fmt": "96.99B"},
"earningsGrowth": {"raw": 0.089, "fmt": "8.9%"},
"returnOnAssets": {"raw": 0.214, "fmt": "21.4%"},
"returnOnEquity": {"raw": 1.342, "fmt": "134.2%"},
"debtToEquity": {"raw": 1.49, "fmt": "149.0%"},
"quickRatio": {"raw": 0.81, "fmt": "0.81"},
"currentRatio": {"raw": 0.97, "fmt": "0.97"},
"totalCash": {"raw": 61100000000, "fmt": "61.1B"},
"totalDebt": {"raw": 105500000000, "fmt": "105.5B"},
"totalCashPerShare": {"raw": 3.90, "fmt": "3.90"},
"earningsQuarterlyGrowth": {"raw": -0.041, "fmt": "-4.1%"},
"revenuePerEmployee": {"raw": 2430000, "fmt": "2.43M"},
"freeCashflow": {"raw": 96920000000, "fmt": "96.92B"}
}
}Campos útiles por módulo
defaultKeyStatistics:
| Campo | Descripción |
|---|---|
beta | Beta (volatilidad vs mercado) |
floatShares | Acciones en float |
sharesOutstanding | Acciones outstanding |
sharesShort | Acciones en corto |
shortRatio | Short ratio (días para cubrir) |
heldPercentInstitutions | % tenencia institucional |
heldPercentInsiders | % tenencia insider |
bookValue | Book value per share |
priceToBook | Price/book ratio |
earningsQuarterlyGrowth | Crecimiento trimestral earnings |
netIncomeToCommon | Net income |
trailingEps | EPS trailing |
forwardEps | EPS forward |
pegRatio | PEG ratio |
lastDividendValue | Último dividendo |
lastDividendDate | Fecha último dividendo |
nextFiscalYearEnd | Fin del próximo año fiscal |
mostRecentQuarter | Último trimestre reportado |
incomeStatementHistory:
{
"incomeStatementHistory": {
"incomeStatementHistory": [
{
"endDate": {"raw": 1704067200, "fmt": "2023-12-31"},
"totalRevenue": {"raw": 383300000000, "fmt": "383.3B"},
"costOfRevenue": {"raw": 214100000000, "fmt": "214.1B"},
"grossProfit": {"raw": 169200000000, "fmt": "169.2B"},
"operatingIncome": {"raw": 114300000000, "fmt": "114.3B"},
"netIncome": {"raw": 97000000000, "fmt": "97B"},
"ebit": {"raw": 114300000000, "fmt": "114.3B"},
"totalOperatingExpenses": {"raw": 269000000000, "fmt": "269B"}
}
],
"maxAge": 86400
}
}---
5. v7/finance/options — Cadena de opciones
Endpoint
GET https://query1.finance.yahoo.com/v7/finance/options/{symbol}
GET https://query1.finance.yahoo.com/v7/finance/options/{symbol}?date={unix_timestamp}Requiere crumb.
Respuesta JSON
{
"optionChain": {
"result": [
{
"underlyingSymbol": "AAPL",
"expirationDates": [1719878400, 1720569600, 1721260800, ...],
"strikes": [170.0, 175.0, 180.0, 185.0, 190.0, 195.0, 200.0, ...],
"hasMiniOptions": false,
"quote": {
"shortName": "Apple Inc.",
"regularMarketPrice": {"raw": 196.89},
"regularMarketChange": {"raw": 2.39},
"regularMarketVolume": {"raw": 45200000},
"fiftyTwoWeekHigh": {"raw": 199.62},
"fiftyTwoWeekLow": {"raw": 141.21},
"marketCap": {"raw": 3020000000000}
},
"options": [
{
"expirationDate": 1719878400,
"hasMiniOptions": false,
"calls": [
{
"contractSymbol": "AAPL240621C00195000",
"strike": {"raw": 195.0, "fmt": "195.00"},
"currency": "USD",
"lastPrice": {"raw": 4.55, "fmt": "4.55"},
"change": {"raw": 0.45, "fmt": "0.45"},
"percentChange": {"raw": 10.97, "fmt": "10.97%"},
"volume": {"raw": 15234, "fmt": "15.2k"},
"openInterest": {"raw": 84500, "fmt": "84.5k"},
"bid": {"raw": 4.50, "fmt": "4.50"},
"ask": {"raw": 4.60, "fmt": "4.60"},
"contractSize": "REGULAR",
"expiration": 1719878400,
"lastTradeDate": 1717444800,
"impliedVolatility": {"raw": 0.281, "fmt": "28.1%"},
"inTheMoney": true
}
],
"puts": [
{
"contractSymbol": "AAPL240621P00195000",
"strike": {"raw": 195.0, "fmt": "195.00"},
"currency": "USD",
"lastPrice": {"raw": 2.85, "fmt": "2.85"},
"change": {"raw": -0.32, "fmt": "-0.32"},
"percentChange": {"raw": -10.09, "fmt": "-10.09%"},
"volume": {"raw": 8900, "fmt": "8.9k"},
"openInterest": {"raw": 62300, "fmt": "62.3k"},
"bid": {"raw": 2.80, "fmt": "2.80"},
"ask": {"raw": 2.90, "fmt": "2.90"},
"contractSize": "REGULAR",
"expiration": 1719878400,
"lastTradeDate": 1717444800,
"impliedVolatility": {"raw": 0.305, "fmt": "30.5%"},
"inTheMoney": false
}
]
}
]
}
],
"error": null
}
}Campos de cada opción
| Campo | Descripción |
|---|---|
contractSymbol | Símbolo OCC de la opción |
strike | Strike price |
lastPrice | Último precio tradeado |
bid | Bid actual |
ask | Ask actual |
volume | Volumen del día |
openInterest | Open interest |
impliedVolatility | Volatilidad implícita |
inTheMoney | Si está ITM (boolean) |
expiration | Timestamp de expiración |
change | Cambio en precio |
percentChange | Cambio porcentual |
contractSize | Tamaño del contrato (REGULAR = 100 acciones) |
Cómo obtener todas las expiraciones
# 1. Obtener fechas de expiración
r = session.get("https://query1.finance.yahoo.com/v7/finance/options/AAPL")
data = r.json()
expirations = data["optionChain"]["result"][0]["expirationDates"]
# 2. Iterar cada fecha
for exp in expirations[:5]: # primeras 5
r = session.get(f"https://query1.finance.yahoo.com/v7/finance/options/AAPL?date={exp}")
data = r.json()
options = data["optionChain"]["result"][0]["options"][0]
calls = options["calls"]
puts = options["puts"]
print(f"Exp {datetime.fromtimestamp(exp)}: {len(calls)} calls, {len(puts)} puts")
time.sleep(0.5)Nota: Las opciones fuera de US stocks generalmente no están disponibles. Para GGAL (BCBA), este endpoint puede devolver vacío.
---
6. v1/finance/search — Búsqueda y noticias
Endpoint
GET https://query1.finance.yahoo.com/v1/finance/search?q={query}No requiere autenticación.
Parámetros
| Parámetro | Default | Descripción |
|---|---|---|
q | — | Término de búsqueda (requerido) |
quotesCount | 10 | Cantidad de quotes a retornar |
newsCount | 10 | Cantidad de noticias a retornar |
enableCb | false | Incluir commercial banking results |
Respuesta JSON
{
"explains": [],
"count": 5,
"quotes": [
{
"symbol": "AAPL",
"isYahooFinance": true,
"exchange": "NMS",
"exchangeName": "NasdaqGS",
"typeDisp": "Equity",
"quoteType": "EQUITY",
"shortname": "Apple Inc.",
"longname": "Apple Inc.",
"sector": "Technology",
"industry": "Consumer Electronics",
"isEligibleForCrossBorder": false
}
],
"news": [
{
"uuid": "some-uuid",
"title": "Apple Hits New All-Time High Ahead of WWDC",
"publisher": "Bloomberg",
"link": "https://finance.yahoo.com/news/...",
"type": "STORY",
"providerPublishTime": 1717444800,
"relatedTickers": ["AAPL"],
"summary": "Apple Inc. shares reached a new all-time high...",
"thumbnail": {
"resolutions": [
{"url": "https://s.yimg.com/...", "width": 200, "height": 200, "tag": "original"}
]
}
}
],
"timeZoneShortName": "EDT"
}Notas
- Ideal para autocompletado y búsqueda de tickers cuando no se sabe el símbolo exacto.
- Las noticias incluyen
thumbnailcon imágenes. - El campo
typeDispayuda a identificar el tipo:Equity,ETF,Mutual Fund,Index, etc. - Si el ticker no existe,
quotesviene vacío pero puede habernews.
---
7. v6/finance/recommendationsbysymbol — Recomendaciones
Endpoint
GET https://query1.finance.yahoo.com/v6/finance/recommendationsbysymbol/{symbol}Requiere crumb.
Respuesta JSON
{
"finance": {
"result": [
{
"symbol": "AAPL",
"recommendedSymbols": [
{"symbol": "MSFT", "score": 0.95},
{"symbol": "GOOGL", "score": 0.88},
{"symbol": "AMZN", "score": 0.82},
{"symbol": "NVDA", "score": 0.79}
]
}
],
"error": null
}
}Devuelve símbolos recomendados similares (no recomendaciones de analistas, eso está en quoteSummary.recommendationTrend).
---
8. v1/finance/trending — Trending symbols
Endpoint
GET https://query1.finance.yahoo.com/v1/finance/trending/{country}No requiere autenticación.
Parámetros
| Parámetro | Valores |
|---|---|
country | US, AU, CA, DE, HK, IN, MX, MY, NZ, SG, UK, VN |
Respuesta JSON
{
"finance": {
"result": [
{
"count": 10,
"quotes": [
{"symbol": "AAPL"},
{"symbol": "NVDA"},
{"symbol": "TSLA"},
{"symbol": "MSFT"},
{"symbol": "AMZN"}
],
"jobTimestamp": 1717444800,
"startInterval": 1717358400
}
],
"error": null
}
}Notas
- Los trending cambian cada ~15 minutos.
USfunciona bien; otros países pueden tener menos datos.
---
9. v1/finance/lookup — Lookup de tickers
Endpoint
GET https://query1.finance.yahoo.com/v1/finance/lookup?query={query}&type=equityNo requiere autenticación.
Parámetros
| Parámetro | Descripción |
|---|---|
query | Término de búsqueda |
type | equity, option, future, currency |
lang | Idioma (default: en-US) |
region | Región (default: US) |
Respuesta JSON
{
"finance": {
"result": [
{"symbol": "AAPL", "name": "Apple Inc.", "type": "EQUITY", "exch": "NMS"}
],
"error": null
}
}---
10. v1/finance/screener — Screener
Endpoint
GET https://query1.finance.yahoo.com/v1/finance/screener?scrIds={scrId}&count={count}Requiere crumb (a veces).
Screeners predefinidos comunes
| scrId | Descripción |
|---|---|
most_actives | Más activos |
day_gainers | Mayores ganadores del día |
day_losers | Mayores perdedores del día |
undervalued_growth_stocks | Crecimiento infravalorados |
aggressive_small_caps | Small caps agresivos |
portfolio_anchors | Anclas de portfolio |
Ejemplo
s = yahoo_session()
r = s.get("https://query1.finance.yahoo.com/v1/finance/screener",
params={"scrIds": "most_actives", "count": 10})
data = r.json()
for quote in data["finance"]["result"][0]["quotes"]:
print(quote["symbol"], quote.get("regularMarketPrice"))---
11. WebSocket streaming
Yahoo Finance tiene un endpoint WebSocket para datos en tiempo real:
wss://streamer.finance.yahoo.com/?version=2Uso básico
import websocket
def on_message(ws, message):
data = json.loads(message)
print(data)
ws = websocket.WebSocketApp("wss://streamer.finance.yahoo.com/?version=2",
on_message=on_message)
ws.run_forever()Los mensajes usan formato Protobuf — no es straight JSON. Requiere manejo de crumb y firma. Es más complejo que los endpoints REST y no está recomendado para uso general. Los endpoints REST con polling cada 20-30 segundos son más estables.
---
12. Rate Limiting y Estrategias
Límites observados
| Límite | Consecuencia |
|---|---|
| ~2 requests/segundo | Límite seguro |
| 3-5 req/s sostenidos | 429 Too Many Requests |
| >10 req/s en ráfaga | IP block temporal (30-60 min) |
| ~2000 req/hora estimado | Límite diario suave |
Estrategia recomendada
import time
import random
def safe_request(func, *args, retries=3, **kwargs):
"""Wrapper con exponential backoff."""
for attempt in range(retries):
try:
resp = func(*args, **kwargs)
if resp.status_code == 429:
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited, waiting {wait:.1f}s...")
time.sleep(wait)
continue
resp.raise_for_status()
return resp.json()
except Exception as e:
if attempt == retries - 1:
raise
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)Rotación de User-Agent
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/119.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/17.1",
]
headers = {"User-Agent": random.choice(USER_AGENTS)}Cacheo de respuestas
Los datos históricos no cambian. Para datos en lote:
import os
import hashlib
import json
CACHE_DIR = ".yf_cache"
os.makedirs(CACHE_DIR, exist_ok=True)
def cached_get(url, params, ttl_seconds=3600):
key = hashlib.md5(f"{url}{json.dumps(params, sort_keys=True)}".encode()).hexdigest()
cache_file = os.path.join(CACHE_DIR, f"{key}.json")
if os.path.exists(cache_file):
age = time.time() - os.path.getmtime(cache_file)
if age < ttl_seconds:
with open(cache_file) as f:
return json.load(f)
resp = requests.get(url, params=params, headers=HEADERS)
data = resp.json()
with open(cache_file, "w") as f:
json.dump(data, f)
return data---
13. Códigos de Error y Troubleshooting
| Error | Causa | Solución |
|---|---|---|
401 Unauthorized | Falta crumb o cookie A3 | Usar yahoo_session() |
429 Too Many Requests | Excediste rate limit | Esperar 30-60s, reducir frecuencia |
{"finance":{"error":{"code":"Bad Request"}}} | Crumb inválido/expirado | Regenerar crumb |
chart.result vacío o null | Ticker inválido, sin datos en ese rango/interval | Verificar símbolo. Cambiar rango |
Quote data missing | El símbolo no tiene quote pública | Verificar que el ticker existe |
| Conexión rechazada | query1.finance.yahoo.com no responde | Fallback a query2.finance.yahoo.com |
Empty JSON {} | Rate limit o bloqueo temporal | Esperar y reintentar con exponential backoff |
chart.error.code: "Not Found" | Símbolo no encontrado | Verificar ticker (ej: usar .BA para argentinos) |
Python-requests/2.xx detectado | User-Agent por defecto | Setear User-Agent de navegador |
| SSL Error | Problemas de red/certificado | Reintentar, verificar conectividad |
Debugging rápido
# Verificar si un ticker existe
r = requests.get(
"https://query1.finance.yahoo.com/v1/finance/lookup",
params={"query": "GGAL", "type": "equity"},
headers=HEADERS
)
print(r.json())
# Verificar crumb
s = requests.Session()
s.headers.update(HEADERS)
s.get("https://fc.yahoo.com")
crumb = s.get("https://query1.finance.yahoo.com/v1/test/getcrumb").text
print(f"Crumb: {crumb}")---
14. Tickers Internacionales
Yahoo Finance maneja tickers de todo el mundo con sufijos de exchange:
| País/Mercado | Sufijo | Ejemplo |
|---|---|---|
| Argentina (BCBA) | .BA | GGAL.BA, YPFD.BA, PAMP.BA |
| Brasil (Bovespa) | .SA | PETR4.SA, VALE3.SA |
| México (BMV) | .MX | WALMEX.MX, CEMEX.CPO.MX |
| Canadá (TSX) | .TO | SHOP.TO, TD.TO |
| Reino Unido (LSE) | .L | HSBA.L, BP.L |
| Alemania (Xetra) | .DE | SAP.DE, DAI.DE |
| Hong Kong (HKEX) | .HK | 0700.HK, 9988.HK |
| Japón (TSE) | .T | 7203.T, 9984.T |
| Australia (ASX) | .AX | CBA.AX, BHP.AX |
| China (Shanghai) | .SS | 600519.SS |
| China (Shenzhen) | .SZ | 000858.SZ |
| India (NSE) | .NS | RELIANCE.NS, TCS.NS |
| India (BSE) | .BO | RELIANCE.BO |
| ETFs | Sin sufijo | SPY, QQQ, ARKK |
| Crypto | -XXX | BTC-USD, ETH-USD, DOGE-USD |
| Forex | =X | EURUSD=X, USDBRL=X |
| Índices | ^ prefix | ^GSPC (S&P 500), ^IXIC (NASDAQ), ^BVSP (Ibovespa) |
Ejemplo con ticker argentino
# GGAL en la Bolsa de Buenos Aires
r = requests.get(
"https://query1.finance.yahoo.com/v8/finance/chart/GGAL.BA",
params={"range": "1y", "interval": "1d"},
headers=HEADERS
)
print(r.json())Importante: No todos los endpoints funcionan para tickers internacionales.v7/optionsgeneralmente solo funciona para US stocks.v10/quoteSummaryfunciona para la mayoría de los mercados.
---
15. Campos Comunes entre Endpoints
Formato raw / fmt
Casi todos los campos numéricos en Yahoo Finance vienen en este formato:
{
"regularMarketPrice": {
"raw": 196.89, # valor numérico para cálculos
"fmt": "196.89" # string formateado para mostrar
}
}Siempre usar .raw para operaciones matemáticas y .fmt para display.
Market states
| Valor | Significado |
|---|---|
PRE | Pre-market (antes de la apertura) |
REGULAR | Mercado abierto en horario regular |
POST | Post-market (después del cierre) |
CLOSED | Mercado cerrado |
Quote types comunes
| quoteType | Descripción |
|---|---|
EQUITY | Acción común |
ETF | Exchange-Traded Fund |
MUTUALFUND | Fondo mutuo |
INDEX | Índice de mercado |
CURRENCY | Par de divisas |
CRYPTOCURRENCY | Criptomoneda |
OPTION | Opción |
FUTURE | Futuro |
BOND | Bono |
---
Apéndice: Resumen de URLs rápidas
# Sin autenticación
GET https://query1.finance.yahoo.com/v8/finance/chart/{symbol}
GET https://query1.finance.yahoo.com/v1/finance/search
GET https://query1.finance.yahoo.com/v1/finance/trending/{country}
GET https://query1.finance.yahoo.com/v1/finance/lookup
GET https://fc.yahoo.com
GET https://query1.finance.yahoo.com/v1/test/getcrumb
# Requieren crumb (usar yahoo_session())
GET https://query1.finance.yahoo.com/v7/finance/quote
GET https://query1.finance.yahoo.com/v10/finance/quoteSummary/{symbol}
GET https://query1.finance.yahoo.com/v7/finance/options/{symbol}
GET https://query1.finance.yahoo.com/v6/finance/recommendationsbysymbol/{symbol}
GET https://query1.finance.yahoo.com/v1/finance/screener---
Este documento se basa en ingeniería inversa de la API no oficial de Yahoo Finance. No hay garantías de disponibilidad o consistencia. Los endpoints pueden cambiar sin aviso.
#!/usr/bin/env python3
"""
Batch fetch de Yahoo Finance con token bucket + ThreadPoolExecutor.
Respeta el rate limit de Yahoo (~2 req/s sostenidos) usando un token bucket
thread-safe. Quote batching: todos los tickers en 1 sola request.
Uso:
# Test seguro (3 tickers, solo chart, 2 req/s)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA --chart --rate 2.0
# Batch completo (quote en 1 request + charts en paralelo)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA,GOOGL,META,AMZN,TSLA --all --rate 2.0 --workers 4
# Solo quotes (siempre 1 request, imposible rate-limit)
py scripts/batch_fetch.py --tickers AAPL,MSFT,NVDA,GOOGL --quote
# Todo con output a archivo
py scripts/batch_fetch.py --tickers AAPL,MSFT --all --rate 2.0 --output data/batch.json
"""
import argparse
import functools
import json
import os
import sys
import threading
import time as _time
from concurrent.futures import ThreadPoolExecutor, as_completed
from curl_cffi import requests
BASE = "https://query1.finance.yahoo.com"
FALLBACK = "https://query2.finance.yahoo.com"
# ---------------------------------------------------------------------------
# Token Bucket - thread-safe, con burst
# ---------------------------------------------------------------------------
class TokenBucket:
"""Token bucket rate limiter. Thread-safe.
Por defecto: 2 tokens/s, max 5 tokens (burst). Esto significa que
pueden dispararse hasta 5 requests instantaneas, luego se estabiliza
a 2 req/s. Yahoo tolera esto sin problemas (yfinance no tiene
rate limiter y manda N requests simultaneas sin throttling).
"""
def __init__(self, rate: float = 2.0, burst: int = 5):
self.rate = rate
self.burst = burst
self.tokens = float(burst)
self.last = _time.monotonic()
self._lock = threading.Lock()
def acquire(self, block: bool = True):
"""Adquiere 1 token. Bloquea hasta que haya disponible."""
if block:
while True:
needed = self._try_consume()
if needed <= 0:
return
_time.sleep(min(needed, 0.1))
def _try_consume(self) -> float:
"""Intenta consumir 1 token. Retorna segundos hasta que haya disponible."""
with self._lock:
now = _time.monotonic()
elapsed = now - self.last
self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
self.last = now
if self.tokens >= 1.0:
self.tokens -= 1.0
return 0.0
return (1.0 - self.tokens) / self.rate
# ---------------------------------------------------------------------------
# Sesion + crumb
# ---------------------------------------------------------------------------
class SessionPool:
"""Pool de sesiones curl_cffi con crumb. Thread-safe."""
def __init__(self, size: int = 2):
self._sessions = [self._new_session() for _ in range(size)]
self._counter = 0
self._lock = threading.Lock()
@staticmethod
def _new_session():
s = requests.Session(impersonate="chrome")
try:
s.get("https://fc.yahoo.com", timeout=10)
crumb = s.get(f"{BASE}/v1/test/getcrumb", timeout=10).text.strip()
s.params = {"crumb": crumb}
except Exception:
s.params = {}
return s
def get(self):
with self._lock:
s = self._sessions[self._counter % len(self._sessions)]
self._counter += 1
return s
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
def fetch_quote(session, tickers):
"""v7/finance/quote - batch de todos los tickers en 1 request."""
symbols = ",".join(tickers)
url = f"{BASE}/v7/finance/quote"
for attempt in range(3):
resp = session.get(url, params={"symbols": symbols}, timeout=15)
if resp.status_code == 429:
_time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Rate limited after 3 retries: quote batch")
def fetch_chart(session, ticker, range_="1y", interval="1d", events="div,splits"):
"""v8/finance/chart - OHLCV historico. No requiere crumb."""
params = {"range": range_, "interval": interval, "events": events}
for attempt in range(3):
resp = session.get(f"{BASE}/v8/finance/chart/{ticker}",
params=params, timeout=15)
if resp.status_code == 429:
_time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Rate limited after 3 retries: chart {ticker}")
def fetch_quote_summary(session, ticker, modules):
"""v10/finance/quoteSummary - fundamentos."""
url = f"{BASE}/v10/finance/quoteSummary/{ticker}"
params = {"modules": ",".join(modules)}
for attempt in range(3):
resp = session.get(url, params=params, timeout=15)
if resp.status_code == 429:
_time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Rate limited after 3 retries: summary {ticker}")
def fetch_options(session, ticker):
"""v7/finance/options - cadena de opciones."""
for attempt in range(3):
resp = session.get(f"{BASE}/v7/finance/options/{ticker}", timeout=15)
if resp.status_code == 429:
_time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Rate limited after 3 retries: options {ticker}")
def fetch_search(ticker):
"""v1/finance/search - busqueda + noticias. Sin crumb."""
url = f"{BASE}/v1/finance/search"
params = {"q": ticker, "quotesCount": 3, "newsCount": 5}
resp = requests.get(url, params=params, impersonate="chrome", timeout=15)
resp.raise_for_status()
return resp.json()
# ---------------------------------------------------------------------------
# Worker - procesa un ticker individual (chart, summary, options)
# ---------------------------------------------------------------------------
CORE_MODULES = [
"assetProfile", "financialData", "defaultKeyStatistics",
"incomeStatementHistory", "balanceSheetHistory", "cashflowStatementHistory",
"earnings", "earningsTrend", "recommendationTrend",
"calendarEvents", "price", "summaryDetail"
]
def worker(ticker, args, bucket, session_pool):
"""Procesa todos los endpoints requeridos para UN ticker."""
result = {"ticker": ticker, "ok": {}, "error": {}}
session = session_pool.get()
if args.chart:
try:
bucket.acquire()
data = fetch_chart(session, ticker, args.range, args.interval)
n_bars = len(data.get("chart", {}).get("result", [{}])[0].get("timestamp", []))
result["ok"]["chart"] = {"bars": n_bars}
except Exception as e:
result["error"]["chart"] = str(e)
if args.fundamentals:
try:
bucket.acquire()
data = fetch_quote_summary(session, ticker, args.modules or CORE_MODULES)
result["ok"]["fundamentals"] = True
except Exception as e:
result["error"]["fundamentals"] = str(e)
if args.options:
try:
bucket.acquire()
data = fetch_options(session, ticker)
opt_res = data.get("optionChain", {}).get("result", [{}])[0]
n_exp = len(opt_res.get("expirationDates", []))
result["ok"]["options"] = {"expirations": n_exp}
except Exception as e:
result["error"]["options"] = str(e)
if args.search:
try:
data = fetch_search(ticker)
n_news = len(data.get("news", []))
result["ok"]["search"] = {"news": n_news}
except Exception as e:
result["error"]["search"] = str(e)
return result
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Batch fetch Yahoo Finance con rate limiting + paralelismo."
)
parser.add_argument("--tickers", "-t", required=True,
help="Tickers separados por coma, ej: AAPL,MSFT,NVDA")
parser.add_argument("--output", "-o", default=None,
help="Archivo JSON de salida")
parser.add_argument("--rate", type=float, default=2.0,
help="Tokens/segundo del rate limiter (default: 2.0)")
parser.add_argument("--burst", type=int, default=5,
help="Max tokens acumulables (default: 5)")
parser.add_argument("--workers", "-w", type=int, default=4,
help="Max workers en ThreadPoolExecutor (default: 4)")
parser.add_argument("--range", default="1y",
help="Rango del chart: 1d..max (default: 1y)")
parser.add_argument("--interval", default="1d",
help="Intervalo del chart (default: 1d)")
parser.add_argument("--modules", default=None,
help="Modulos de quoteSummary separados por coma")
# Que endpoints ejecutar
parser.add_argument("--all", action="store_true",
help="Todos los endpoints")
parser.add_argument("--chart", action="store_true",
help="Incluir historico OHLCV")
parser.add_argument("--quote", action="store_true",
help="Incluir quote batch (todos los tickers en 1 request)")
parser.add_argument("--fundamentals", action="store_true",
help="Incluir fundamentals (1 req por ticker)")
parser.add_argument("--options", action="store_true",
help="Incluir opciones (1 req por ticker)")
parser.add_argument("--search", action="store_true",
help="Incluir busqueda + noticias (1 req por ticker)")
args = parser.parse_args()
if args.all:
args.chart = True
args.quote = True
args.fundamentals = True
args.options = True
args.search = True
tickers = [t.strip().upper() for t in args.tickers.split(",")]
if args.modules:
args.modules = [m.strip() for m in args.modules.split(",")]
else:
args.modules = CORE_MODULES
bucket = TokenBucket(rate=args.rate, burst=args.burst)
session_pool = SessionPool(size=min(args.workers, 4))
output = {
"meta": {
"tickers": tickers,
"rate": args.rate,
"burst": args.burst,
"workers": args.workers,
"timestamp": _time.strftime("%Y-%m-%dT%H:%M:%SZ", _time.gmtime()),
"note": "Datos obtenidos de la API no oficial de Yahoo Finance."
},
"quote": None,
"tickers": {},
"summary": {
"total": len(tickers),
"ok": 0,
"errors": 0
}
}
# --- Fase 1: Quote batch (1 request para TODOS los tickers) ---
if args.quote:
print(f">> Quote batch ({len(tickers)} tickers)...")
try:
session = session_pool.get()
data = fetch_quote(session, tickers)
output["quote"] = data
results = data.get("quoteResponse", {}).get("result", [])
for q in results:
sym = q.get("symbol", "?")
p = q.get("regularMarketPrice", "N/A")
chg = q.get("regularMarketChangePercent", 0)
print(f" {sym}: ${p} ({chg:+.2f}%)")
output["summary"]["quotes_ok"] = len(results)
except Exception as e:
print(f" ERR quote batch: {e}")
output["summary"]["quote_error"] = str(e)
# --- Fase 2: Paralelo por ticker (chart, fundamentals, options) ---
endpoints_after_quote = sum([args.chart, args.fundamentals, args.options, args.search])
if endpoints_after_quote == 0:
pass # solo quote
else:
print(f">> Procesando {len(tickers)} tickers con {args.workers} workers, rate={args.rate} req/s...")
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {
pool.submit(worker, t, args, bucket, session_pool): t
for t in tickers
}
for future in as_completed(futures):
t = futures[future]
try:
result = future.result()
output["tickers"][t] = result
n_ok = len(result["ok"])
n_err = len(result["error"])
if n_err > 0:
output["summary"]["errors"] += 1
print(f" {t}: {n_ok} ok, {n_err} errors")
else:
output["summary"]["ok"] += 1
print(f" {t}: {n_ok} ok")
except Exception as e:
output["tickers"][t] = {"ticker": t, "error": str(e)}
output["summary"]["errors"] += 1
print(f" {t}: EXCEPTION {e}")
# --- Output ---
outpath = args.output or f"batch_{len(tickers)}t.json"
with open(outpath, "w", encoding="utf-8") as f:
json.dump(output, f, indent=2, default=str)
size_kb = os.path.getsize(outpath) / 1024
print(f"\nGuardado: {outpath} ({size_kb:.0f} KB)")
print(f"Tickers: {output['summary']['ok']} ok / {output['summary']['errors']} errors")
if __name__ == "__main__":
main()
"""
Descargar datos históricos OHLCV de Yahoo Finance usando la API v8/chart directa.
Sin dependencia de yfinance — solo requests.
Uso:
python download_historical.py --tickers AAPL,MSFT --range 1y --interval 1d --output data/
python download_historical.py --tickers AAPL --period1 1672531200 --period2 1704067200 --interval 1d
"""
import argparse
import csv
import os
import sys
import time
from datetime import datetime, timezone
import requests
BASE_URL = "https://query1.finance.yahoo.com/v8/finance/chart"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
def fetch_chart(ticker, period1=None, period2=None, range_=None, interval="1d", events="div,splits"):
"""Fetch raw JSON from v8/chart endpoint."""
params = {"interval": interval}
if range_:
params["range"] = range_
else:
if period1:
params["period1"] = period1
if period2:
params["period2"] = period2 or int(time.time())
if events:
params["events"] = events
url = f"{BASE_URL}/{ticker}"
resp = requests.get(url, params=params, headers=HEADERS)
resp.raise_for_status()
return resp.json()
def parse_chart(data, ticker):
"""Parse JSON de v8/chart a lista de dicts planos."""
try:
result = data["chart"]["result"][0]
except (KeyError, IndexError, TypeError):
print(f" ⚠ Sin datos para {ticker}")
return []
timestamps = result.get("timestamp", [])
quotes = result.get("indicators", {}).get("quote", [{}])[0]
adjclose = result.get("indicators", {}).get("adjclose", [{}])[0]
events = result.get("events", {})
rows = []
for i, ts in enumerate(timestamps):
row = {
"ticker": ticker,
"date": datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d"),
"open": _get(quotes, "open", i),
"high": _get(quotes, "high", i),
"low": _get(quotes, "low", i),
"close": _get(quotes, "close", i),
"volume": _get(quotes, "volume", i),
"adjclose": _get(adjclose, "adjclose", i),
}
rows.append(row)
# Agregar dividendos como filas separadas
if events and "dividends" in events:
for ts_str, div in events["dividends"].items():
rows.append({
"ticker": ticker,
"date": datetime.fromtimestamp(int(ts_str), tz=timezone.utc).strftime("%Y-%m-%d"),
"dividend": div.get("amount"),
})
return rows
def _get(obj, key, idx):
"""Get value safely from a list at index."""
try:
val = obj[key][idx]
return val if val is not None else ""
except (IndexError, KeyError, TypeError):
return ""
def save_csv(rows, output_path, ticker):
"""Save rows to CSV file."""
if not rows:
print(f" ⚠ No hay datos para guardar para {ticker}")
return
filename = os.path.join(output_path, f"{ticker}_historical.csv")
fieldnames = rows[0].keys()
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f" ✓ {filename} — {len(rows)} filas")
def main():
parser = argparse.ArgumentParser(description="Descargar históricos OHLCV de Yahoo Finance")
parser.add_argument("--tickers", required=True, help="Tickers separados por coma, ej: AAPL,MSFT")
parser.add_argument("--range", default="1mo", help="Rango: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max")
parser.add_argument("--interval", default="1d", help="Intervalo: 1m, 5m, 15m, 1h, 1d, 1wk, 1mo")
parser.add_argument("--period1", type=int, help="Timestamp Unix inicio (alternativo a --range)")
parser.add_argument("--period2", type=int, help="Timestamp Unix fin (alternativo a --range)")
parser.add_argument("--output", default="data", help="Directorio de salida")
parser.add_argument("--delay", type=float, default=0.5, help="Delay entre requests (segundos)")
args = parser.parse_args()
os.makedirs(args.output, exist_ok=True)
tickers = [t.strip().upper() for t in args.tickers.split(",")]
for ticker in tickers:
print(f"→ Descargando {ticker} ...")
try:
data = fetch_chart(
ticker,
period1=args.period1,
period2=args.period2,
range_=args.range,
interval=args.interval,
)
rows = parse_chart(data, ticker)
save_csv(rows, args.output, ticker)
except requests.exceptions.RequestException as e:
print(f" ✗ Error en {ticker}: {e}")
except Exception as e:
print(f" ✗ Error inesperado en {ticker}: {e}")
time.sleep(args.delay)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Fetch integral de Yahoo Finance: histórico, quote, fundamentals, opciones, búsqueda y noticias.
Usa requests HTTP directos — sin yfinance ni wrappers.
Uso:
# Todo lo disponible para un ticker
python fetch_all.py --ticker AAPL --all
# Solo histórico con rango personalizado
python fetch_all.py --ticker MSFT --chart --range 5y --interval 1wk
# Histórico por timestamps Unix
python fetch_all.py --ticker TSLA --chart --period1 1609459200 --period2 1704067200
# Solo quote + fundamentals con módulos específicos
python fetch_all.py --ticker NVDA --quote --fundamentals --modules financialData,assetProfile
# Todo + opciones para varios tickers (uno por vez)
python fetch_all.py --ticker AAPL --all --output data/aapl.json
"""
import argparse
import json
import os
import sys
import time
from datetime import datetime, timezone
import requests
BASE = "https://query1.finance.yahoo.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
# =========================================================================
# Sesión con autenticación
# =========================================================================
def yahoo_session():
"""Crea una requests.Session con cookie A3 y crumb para endpoints auth."""
s = requests.Session()
s.headers.update(HEADERS)
s.get("https://fc.yahoo.com", timeout=10)
crumb = s.get(f"{BASE}/v1/test/getcrumb", timeout=10).text.strip()
s.params = {"crumb": crumb}
return s
# =========================================================================
# Endpoints
# =========================================================================
def fetch_chart(ticker, range_="1mo", interval="1d", events="div,splits",
period1=None, period2=None, include_prepost=False):
"""
v8/finance/chart — Históricos OHLCV.
No requiere autenticación, sólo User-Agent.
"""
params = {"interval": interval}
if range_:
params["range"] = range_
if period1:
params["period1"] = period1
if period2:
params["period2"] = period2
else:
params["period2"] = int(time.time())
if events:
params["events"] = events
if include_prepost:
params["includePrePost"] = "true"
r = requests.get(f"{BASE}/v8/finance/chart/{ticker}",
params=params, headers=HEADERS, timeout=15)
r.raise_for_status()
return r.json()
def fetch_quote(session, ticker):
"""
v7/finance/quote — Precio en tiempo real y métricas básicas.
Requiere crumb.
"""
r = session.get(f"{BASE}/v7/finance/quote",
params={"symbols": ticker}, timeout=15)
r.raise_for_status()
return r.json()
def fetch_quote_summary(session, ticker, modules):
"""
v10/finance/quoteSummary/{ticker} — Fundamentos y datos profundos.
Requiere crumb.
"""
r = session.get(
f"{BASE}/v10/finance/quoteSummary/{ticker}",
params={"modules": ",".join(modules)},
timeout=15
)
r.raise_for_status()
return r.json()
def fetch_options(session, ticker, expiration_date=None):
"""
v7/finance/options/{ticker} — Cadena de opciones.
Requiere crumb.
expiration_date: timestamp Unix opcional para una fecha específica.
"""
url = f"{BASE}/v7/finance/options/{ticker}"
params = {}
if expiration_date:
params["date"] = expiration_date
r = session.get(url, params=params, timeout=15)
r.raise_for_status()
return r.json()
def fetch_search(ticker, quotes_count=3, news_count=5):
"""
v1/finance/search — Búsqueda + noticias.
No requiere autenticación.
"""
r = requests.get(
f"{BASE}/v1/finance/search",
params={"q": ticker, "quotesCount": quotes_count, "newsCount": news_count},
headers=HEADERS, timeout=15
)
r.raise_for_status()
return r.json()
def fetch_recommendations(session, ticker):
"""
v6/finance/recommendationsbysymbol/{ticker} — Recomendaciones de analistas.
Requiere crumb.
"""
r = session.get(f"{BASE}/v6/finance/recommendationsbysymbol/{ticker}", timeout=15)
r.raise_for_status()
return r.json()
def fetch_trending(session=None, country="US"):
"""
v1/finance/trending/{country} — Trending symbols.
No requiere autenticación.
"""
r = requests.get(f"{BASE}/v1/finance/trending/{country}",
headers=HEADERS, timeout=15)
r.raise_for_status()
return r.json()
# =========================================================================
# Módulos disponibles para quoteSummary
# =========================================================================
ALL_MODULES = [
"assetProfile", # Perfil de la empresa completo
"summaryProfile", # Resumen del perfil
"financialData", # Métricas financieras clave
"defaultKeyStatistics", # Estadísticas clave (beta, shares, etc.)
"incomeStatementHistory", # Estado de resultados histórico
"incomeStatementHistoryQuarterly", # Estado de resultados trimestral
"balanceSheetHistory", # Balance general histórico
"balanceSheetHistoryQuarterly", # Balance general trimestral
"cashflowStatementHistory", # Flujo de caja histórico
"cashflowStatementHistoryQuarterly", # Flujo de caja trimestral
"earnings", # Ganancias históricas
"earningsHistory", # Historia de earnings vs estimados
"earningsTrend", # Tendencia de earnings
"recommendationTrend", # Recomendaciones de analistas
"upgradeDowngradeHistory", # Historia de upgrades/downgrades
"insiderTransactions", # Transacciones de insider
"insiderHolders", # Tenedores insider
"institutionOwnership", # Tenencia institucional
"fundOwnership", # Tenencia de fondos mutuos
"majorDirectHolders", # Mayores tenedores directos
"majorHoldersBreakdown", # Desglose de tenedores (%, institutional, insider)
"secFilings", # SEC filings
"calendarEvents", # Próximos eventos
"price", # Información detallada de precio
"quoteType", # Tipo de instrumento
"summaryDetail", # Detalle resumido
"symbol", # Símbolo
"topHoldings", # Top holdings (ETFs)
"fundProfile", # Perfil del fondo (ETFs)
"indexTrend", # Tendencia del índice
"sectorTrend", # Tendencia del sector
"industryTrend", # Tendencia de la industria
"netSharePurchaseActivity", # Actividad neta de recompra
"esgScore", # Score ESG (si disponible)
]
# Módulos esenciales para un fetch rápido
CORE_MODULES = [
"assetProfile", "financialData", "defaultKeyStatistics",
"incomeStatementHistory", "balanceSheetHistory", "cashflowStatementHistory",
"earnings", "earningsTrend", "recommendationTrend",
"calendarEvents", "price", "summaryDetail"
]
# =========================================================================
# Main
# =========================================================================
def main():
parser = argparse.ArgumentParser(
description="Fetch integral de Yahoo Finance. Usa requests HTTP directos (sin yfinance)."
)
parser.add_argument("--ticker", "-t", required=True,
help="Ticker a consultar (ej: AAPL, MSFT, GGAL)")
parser.add_argument("--output", "-o", default=None,
help="Archivo JSON de salida (default: <ticker>_fetch.json)")
parser.add_argument("--delay", type=float, default=0.5,
help="Delay entre requests en segundos (default: 0.5)")
# Qué endpoints ejecutar
parser.add_argument("--all", action="store_true",
help="Fetch de todo lo disponible (chart + quote + all fundamentals + options + search)")
parser.add_argument("--chart", action="store_true",
help="Incluir histórico OHLCV")
parser.add_argument("--quote", action="store_true",
help="Incluir quote en tiempo real")
parser.add_argument("--fundamentals", action="store_true",
help="Incluir fundamentals (quoteSummary)")
parser.add_argument("--options", action="store_true",
help="Incluir cadena de opciones")
parser.add_argument("--search", action="store_true",
help="Incluir búsqueda y noticias")
parser.add_argument("--recommendations", action="store_true",
help="Incluir recomendaciones de analistas")
parser.add_argument("--trending", action="store_true",
help="Incluir trending symbols")
# Parámetros para chart
parser.add_argument("--range", default="1y",
help="Rango del histórico: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max")
parser.add_argument("--interval", default="1d",
help="Intervalo: 1m, 2m, 5m, 15m, 30m, 60m, 1h, 1d, 1wk, 1mo")
parser.add_argument("--period1", type=int, default=None,
help="Timestamp Unix inicio (alternativo a --range)")
parser.add_argument("--period2", type=int, default=None,
help="Timestamp Unix fin (alternativo a --range)")
# Parámetros para fundamentals
parser.add_argument("--modules", default=None,
help="Módulos de quoteSummary separados por coma (default: core)")
parser.add_argument("--all-modules", action="store_true",
help="Fetch de TODOS los módulos de quoteSummary (~33 módulos)")
# Consola
parser.add_argument("--quiet", "-q", action="store_true",
help="Sin salida en consola (sólo errores)")
args = parser.parse_args()
# Si --all, habilitar todo
if args.all:
args.chart = True
args.quote = True
args.fundamentals = True
args.options = True
args.search = True
args.recommendations = True
ticker = args.ticker.upper()
results = {
"ticker": ticker,
"timestamp": datetime.now(timezone.utc).isoformat(),
"endpoints": {},
"meta": {
"note": "Datos obtenidos de la API no oficial de Yahoo Finance. Sin garantías."
}
}
errors = []
log = [] if args.quiet else print
def log_msg(msg):
if not args.quiet:
print(msg)
session = None # lazy init
# ------ CHART ------
if args.chart:
log_msg(f">> Chart ({args.range}, {args.interval})...")
try:
data = fetch_chart(ticker, range_=args.range, interval=args.interval,
period1=args.period1, period2=args.period2)
results["endpoints"]["chart"] = data
n = len(data.get("chart", {}).get("result", [{}])[0].get("timestamp", []))
log_msg(f" OK {n} bars")
except Exception as e:
errors.append(f"chart: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ QUOTE ------
if args.quote:
log_msg(">> Quote...")
try:
if session is None:
session = yahoo_session()
data = fetch_quote(session, ticker)
results["endpoints"]["quote"] = data
q = data.get("quoteResponse", {}).get("result", [{}])[0]
p = q.get("regularMarketPrice", "N/A")
chg = q.get("regularMarketChangePercent", 0)
log_msg(f" OK Price: ${p} ({chg:.2f}%)")
except Exception as e:
errors.append(f"quote: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ FUNDAMENTALS ------
if args.fundamentals:
if args.all_modules:
modules = ALL_MODULES
elif args.modules:
modules = [m.strip() for m in args.modules.split(",")]
else:
modules = CORE_MODULES
log_msg(f">> Fundamentals ({len(modules)} módulos)...")
try:
if session is None:
session = yahoo_session()
data = fetch_quote_summary(session, ticker, modules)
results["endpoints"]["quoteSummary"] = data
fin = data.get("quoteSummary", {}).get("result", [{}])[0]
profile = fin.get("assetProfile", {})
summary = profile.get("longBusinessSummary", "N/A")[:100]
log_msg(f" OK Company: {summary}...")
fd = fin.get("financialData", {})
log_msg(f" Revenue: {fd.get('totalRevenue',{}).get('fmt','N/A')} "
f"EBITDA: {fd.get('ebitda',{}).get('fmt','N/A')} "
f"PE: {fd.get('trailingPE',{}).get('fmt','N/A')}")
except Exception as e:
errors.append(f"fundamentals: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ OPTIONS ------
if args.options:
log_msg(">> Options...")
try:
if session is None:
session = yahoo_session()
data = fetch_options(session, ticker)
results["endpoints"]["options"] = data
opt_res = data.get("optionChain", {}).get("result", [{}])[0]
exp = len(opt_res.get("expirationDates", []))
opts = opt_res.get("options", [])
nc = len(opts[0].get("calls", [])) if opts else 0
np_ = len(opts[0].get("puts", [])) if opts else 0
log_msg(f" OK {exp} expiration dates, {nc} calls, {np_} puts")
except Exception as e:
errors.append(f"options: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ SEARCH ------
if args.search:
log_msg(">> Search + News...")
try:
data = fetch_search(ticker)
results["endpoints"]["search"] = data
nn = len(data.get("news", []))
log_msg(f" OK {nn} news items")
except Exception as e:
errors.append(f"search: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ RECOMMENDATIONS ------
if args.recommendations:
log_msg(">> Recommendations...")
try:
if session is None:
session = yahoo_session()
data = fetch_recommendations(session, ticker)
results["endpoints"]["recommendations"] = data
log_msg(" OK")
except Exception as e:
errors.append(f"recommendations: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ------ TRENDING ------
if args.trending:
log_msg(">> Trending...")
try:
data = fetch_trending()
results["endpoints"]["trending"] = data
nq = len(data.get("finance", {}).get("result", [{}])[0].get("quotes", []))
log_msg(f" OK {nq} trending symbols")
except Exception as e:
errors.append(f"trending: {e}")
log_msg(f" ERR {e}")
time.sleep(args.delay)
# ====== GUARDAR ======
results["errors"] = errors
results["endpoints_count"] = len(results["endpoints"])
if args.output:
outpath = args.output
else:
outpath = f"{ticker}_fetch.json"
with open(outpath, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, default=str)
size_kb = os.path.getsize(outpath) / 1024
log_msg(f"\n{'='*50}")
log_msg(f"Guardado en: {outpath}")
log_msg(f"Endpoints exitosos: {results['endpoints_count']}/{len(errors) + results['endpoints_count']}")
if errors:
log_msg(f"Errores ({len(errors)}):")
for e in errors:
log_msg(f" - {e}")
log_msg(f"Tamano: {size_kb:.0f} KB")
if __name__ == "__main__":
main()
"""
Fetch quote + fundamentals de Yahoo Finance usando la API v7/v10 directa.
Sin dependencia de yfinance — solo requests + cookie/crumb.
Uso:
python fetch_quote.py --tickers AAPL,MSFT --output data/
python fetch_quote.py --tickers AAPL --modules assetProfile,financialData --json
"""
import argparse
import json
import os
import sys
import time
import requests
BASE = "https://query1.finance.yahoo.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
def yahoo_session():
"""Create requests.Session with cookie A3 and crumb."""
s = requests.Session()
s.headers.update(HEADERS)
s.get("https://fc.yahoo.com", timeout=10)
crumb_resp = s.get(f"{BASE}/v1/test/getcrumb", timeout=10)
crumb = crumb_resp.text.strip()
s.params = {"crumb": crumb}
return s
def fetch_quote(session, tickers):
"""Fetch real-time quote via v7/quote."""
symbols = ",".join(tickers)
url = f"{BASE}/v7/finance/quote"
resp = session.get(url, params={"symbols": symbols})
resp.raise_for_status()
return resp.json()
def fetch_quote_summary(session, ticker, modules):
"""Fetch fundamentals via v10/quoteSummary."""
url = f"{BASE}/v10/finance/quoteSummary/{ticker}"
resp = session.get(url, params={"modules": ",".join(modules)})
resp.raise_for_status()
return resp.json()
def main():
parser = argparse.ArgumentParser(description="Fetch quote + fundamentals de Yahoo Finance")
parser.add_argument("--tickers", required=True, help="Tickers separados por coma, ej: AAPL,MSFT")
parser.add_argument("--modules", default="assetProfile,financialData,defaultKeyStatistics",
help="Módulos de quoteSummary separados por coma")
parser.add_argument("--output", default="data", help="Directorio de salida")
parser.add_argument("--json", action="store_true", help="Output a JSON en stdout")
parser.add_argument("--delay", type=float, default=1.0, help="Delay entre requests (segundos)")
args = parser.parse_args()
os.makedirs(args.output, exist_ok=True)
tickers = [t.strip().upper() for t in args.tickers.split(",")]
modules = [m.strip() for m in args.modules.split(",")]
print("→ Obteniendo sesión con crumb...")
session = yahoo_session()
# 1. Quote rápido
print(f"→ Fetching quote para {', '.join(tickers)}...")
try:
quote_data = fetch_quote(session, tickers)
if args.json:
print(json.dumps(quote_data, indent=2))
else:
outfile = os.path.join(args.output, "quotes.json")
with open(outfile, "w", encoding="utf-8") as f:
json.dump(quote_data, f, indent=2)
print(f" ✓ quotes.json guardado en {outfile}")
# Mostrar resumen
for q in quote_data.get("quoteResponse", {}).get("result", []):
print(f" {q['symbol']}: ${q.get('regularMarketPrice', 'N/A')} "
f"({q.get('regularMarketChangePercent', 0):.2f}%) "
f"vol={q.get('regularMarketVolume', 'N/A')}")
except Exception as e:
print(f" ✗ Error en quote: {e}")
time.sleep(args.delay)
# 2. QuoteSummary (fundamentals) por ticker
for ticker in tickers:
print(f"\n→ Fetching fundamentals para {ticker}...")
try:
summary = fetch_quote_summary(session, ticker, modules)
if args.json:
print(json.dumps(summary, indent=2))
else:
outfile = os.path.join(args.output, f"{ticker}_fundamentals.json")
with open(outfile, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2)
print(f" ✓ {ticker}_fundamentals.json guardado")
# Mostrar resumen si hay financialData
result = summary.get("quoteSummary", {}).get("result", [{}])[0]
fin = result.get("financialData", {})
if fin:
print(f" Revenue: {fin.get('totalRevenue', {}).get('raw', 'N/A')}")
print(f" EBITDA: {fin.get('ebitda', {}).get('raw', 'N/A')}")
print(f" Profit Margin: {fin.get('profitMargins', {}).get('fmt', 'N/A')}")
except Exception as e:
print(f" ✗ Error en {ticker}: {e}")
time.sleep(args.delay)
if __name__ == "__main__":
main()