Lección 1.3.2 · 50 min

Fuentes de datos gratuitas

Para aprender y para investigar hay mucho dato gratuito, pero no todo es igual: unas fuentes son oficiales y estables, otras dependen de una web que no las ofrece como servicio. Esta lección recorre siete, con sus reglas de acceso leídas en su propia documentación, y procesa el formato de respuesta de cada API.

Fig. — Qué tipo de dato da cada fuente gratuita de la lección (Stooq no aparece porque su descarga directa no funcionaba el 21/10/2026) (esquema)
02/08
Módulo
1.3 · Fuentes de datos de mercado
Duración
50 min
Lecciones del nivel 1
  1. 1.1 · Cómo funcionan los mercados

    1. 1.1.1 Participantes y centros de negociación
    2. 1.1.2 Sesiones, subastas, festivos y calendarios
    3. 1.1.3 Libro de órdenes: bid, ask, spread y profundidad
    4. 1.1.4 Tipos de órdenes
    5. 1.1.5 Costes, liquidación, apalancamiento y margen
  2. 1.2 · Clases de activos

    1. 1.2.1 Acciones: splits, dividendos y eventos corporativos
    2. 1.2.2 ETFs: réplica, tracking error, apalancados y UCITS
    3. 1.2.3 Futuros: especificaciones, margen, roll y contango
    4. 1.2.4 Forex: pares, pips, lotes, rollover y CFD
    5. 1.2.5 Opciones: call, put, valor intrínseco y valor temporal
    6. 1.2.6 Cripto: spot, perpetuos, funding y custodia
    7. 1.2.7 Renta fija y tipos de interés; CFDs y productos apalancados
  3. 1.3 · Fuentes de datos de mercado

    1. 1.3.1 Tipos de datos de mercado: EOD, barras, ticks, libro y referencia
    2. 1.3.2 Fuentes de datos gratuitas
    3. 1.3.3 APIs de datos retail y prosumer
    4. 1.3.4 Proveedores de datos profesionales
    5. 1.3.5 Datos de cripto y on-chain
    6. 1.3.6 Brókeres como fuente de datos
    7. 1.3.7 Licencias y términos de uso de datos
    8. 1.3.8 Cómo elegir proveedor de datos
  4. 1.4 · Ingeniería de datos financieros

    1. 1.4.1 Descarga por REST y websockets: paginación, límites y reintentos
    2. 1.4.2 Limpieza de datos: faltantes, ticks erróneos, huecos y zonas horarias
    3. 1.4.3 Ajustes por splits y dividendos: precios ajustados y sin ajustar
    4. 1.4.4 Universos sin sesgo de supervivencia: deslistados, fusiones y cambios de símbolo
    5. 1.4.5 Construcción de barras: tiempo, ticks, volumen, dólar y desequilibrio
    6. 1.4.6 Series continuas de futuros: reglas de roll, ajuste por diferencia y ajuste por ratio
    7. 1.4.7 Almacenamiento: CSV, Parquet, particionado, DuckDB, TimescaleDB y ArcticDB
    8. 1.4.8 Pipeline reproducible: descarga, validación, actualización incremental y tests de calidad
  5. 1.5 · Datos fundamentales y macro

    1. 1.5.1 Estados financieros, ratios, XBRL y SEC EDGAR
    2. 1.5.2 Datos macro, calendarios económicos, estimaciones y transcripciones
  6. 1.6 · Análisis exploratorio financiero

    1. 1.6.1 Rentabilidades, volatilidad, correlaciones móviles y drawdown
    2. 1.6.2 Hechos estilizados: colas gruesas, agrupamiento, autocorrelación y apalancamiento
    3. 1.6.3 Estacionalidad: día de la semana, mes, cambio de mes y sesiones intradía
  7. 1.7 · Análisis técnico programado

    1. 1.7.1 Indicadores técnicos en Python
    2. 1.7.2 Implementación propia frente a TA-Lib: validar resultados
    3. 1.7.3 Patrones de velas y soportes y resistencias
    4. 1.7.4 Multi-timeframe sin anticipación

Dos clases de «gratis»

Hay mucho dato financiero gratuito, y casi todo el que aprende empieza por él. Pero bajo la misma palabra hay dos cosas muy distintas:

  • Servicios oficiales: un banco central, un regulador o una agencia estadística publica sus datos con una API documentada, unas condiciones de uso y una intención de mantenerla. FRED (Reserva Federal de San Luis), EDGAR (SEC), el BCE, el Banco de España y la CNMV son de esta clase.
  • Herramientas no oficiales: una librería lee los datos de una web que no los ofrece como servicio para terceros. yfinance es la más conocida. Funciona, y mucho, hasta el día en que la web cambia.

La diferencia importa para un sistema. Un servicio oficial puede cambiar de versión, pero lo anuncia y documenta. Una herramienta no oficial puede dejar de funcionar sin aviso, cambiar el formato o devolver límites de peticiones que nadie ha documentado.

¿Qué necesitas?PreciosMacro y tiposEmpresasyfinanceFREDBCEBanco de EspañaSEC EDGARCNMV
Fuente Mermaid
flowchart LR
  Q{¿Qué necesitas?} --> P[Precios]
  Q --> M[Macro y tipos]
  Q --> E[Empresas]
  P --> Y[yfinance]
  M --> F[FRED]
  M --> B[BCE]
  M --> BE[Banco de España]
  E --> S[SEC EDGAR]
  E --> C[CNMV]

La matriz de la cabecera resume qué tipo de dato da cada una. Los huecos importan tanto como los cuadros: ninguna fuente oficial gratuita da precios de acciones con historia larga y ajustes corporativos fiables. Para eso están los proveedores de las lecciones 1.3.3 y 1.3.4.

yfinance: útil para aprender, frágil para operar

yfinance es una librería de código abierto (licencia Apache) que descarga precios, dividendos, splits y algunos datos de empresa de Yahoo Finance. Su README es explícito1Nota 1 · README de yfinance y documentación, consultados el 21/10/2026; versión 1.7.0 en PyPI.: no está afiliada, respaldada ni revisada por Yahoo; usa las APIs que Yahoo expone públicamente; está pensada para investigación y educación; y hay que consultar los términos de uso de Yahoo para saber qué derechos hay sobre los datos descargados, recordando que la API de Yahoo Finance es solo para uso personal. La lección 1.3.7 explica qué significa eso al publicar o compartir resultados.

IN [01]yfinance_descarga.pypython
import yfinance as yf                # uv add yfinance (necesita red)

aapl = yf.Ticker("AAPL")
diario = aapl.history(period="1mo")
varios = yf.download(["MSFT", "AAPL"], start="2025-01-01",
                     end="2025-12-31", auto_adjust=True)

Tres cosas que conviene saber antes de usarla:

  • Ajusta por defecto. download tiene auto_adjust=True: los precios vienen ajustados por dividendos y splits. Si se comparan con precios negociados de otra fuente, no cuadran (lecciones 1.3.8 y 1.4.3).
  • Límites no documentados. El README no habla de límites de peticiones, pero el código define una excepción YFRateLimitError («Too Many Requests») que se lanza cuando Yahoo responde 429. Una descarga de cientos de tickers seguidos puede cortarse a mitad.
  • Versiones frecuentes. La versión 1.7.0 es del 26 de agosto de 2026, tras otras dos en julio y agosto. Cuando Yahoo cambia algo, la librería tiene que adaptarse; fija la versión en el pyproject.toml (lección 0.2.2).

FRED: macro y tipos, con versiones

FRED, de la Reserva Federal de San Luis, reúne series económicas de muchos organismos: tipos de interés, inflación, empleo, tipos de cambio. Su API exige una clave gratuita (una cadena de 32 caracteres) en cada petición y admite hasta 120 peticiones por minuto; por encima responde 429, y no respetar el límite puede acabar en un bloqueo temporal2Nota 2 · FRED API: series/observations, API key y errores («Up to 120 requests per minute»), consultados el 21/10/2026..

IN [02]fred_observaciones.pypython
import os
from urllib.parse import urlencode
from urllib.request import urlopen

params = {"series_id": "DGS10", "file_type": "json",
          "observation_start": "2025-01-01",
          "api_key": os.environ["FRED_API_KEY"]}
url = ("https://api.stlouisfed.org/fred/series/observations?"
       + urlencode(params))
with urlopen(url, timeout=10) as r:
    datos = r.read()

La respuesta JSON tiene metadatos de la consulta (realtime_start, realtime_end, observation_start, count, offset, limit…) y una lista observations con un diccionario por fecha: date, value y el periodo de tiempo real en que ese valor era el vigente. El detalle que más errores causa: value llega como texto. Una muestra ilustrativa con esa estructura:

IN [03]python
import json

import pandas as pd

fred_txt = """{
  "realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
  "observation_start": "2025-01-01", "count": 3, "offset": 0,
  "limit": 100000,
  "observations": [
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-02", "value": "4.57"},
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-03", "value": "4.60"},
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-06", "value": "4.62"}
  ]
}"""
fred = json.loads(fred_txt)
obs = pd.DataFrame(fred["observations"])
print(obs.dtypes.to_dict())
obs["date"] = pd.to_datetime(obs["date"])
obs["value"] = pd.to_numeric(obs["value"], errors="coerce")
obs[["date", "value"]]
OUT [03]Respuesta de FRED: valores como texto
{'realtime_start': <StringDtype(na_value=nan)>, 'realtime_end': <StringDtype(na_value=nan)>, 'date': <StringDtype(na_value=nan)>, 'value': <StringDtype(na_value=nan)>}
        date  value
0 2025-01-02   4.57
1 2025-01-03   4.60
2 2025-01-06   4.62

Sin to_numeric, obs["value"].mean() fallaría y obs["value"] + obs["value"] concatenaría cadenas. Con errors="coerce", cualquier valor que no sea un número pasa a NaN en lugar de romper la conversión.

Los dos campos realtime_* son la puerta a una de las funciones más valiosas de FRED: los vintages. Las series macro se revisan (el PIB, el empleo); realtime_start, realtime_end y vintage_dates permiten pedir los datos tal como existían en una fecha pasada, la base de ALFRED, el archivo de versiones de FRED. Con una muestra ilustrativa de una serie revisada:

IN [04]python
versiones = pd.DataFrame({
    "date": ["2025-03-01", "2025-03-01", "2025-03-01"],
    "value": [151.0, 228.0, 205.0],
    "realtime_start": ["2025-04-04", "2025-05-02", "2025-06-06"],
    "realtime_end": ["2025-05-01", "2025-06-05", "9999-12-31"],
})

def conocido_el(v, fecha):
    desde, hasta = v["realtime_start"], v["realtime_end"]
    vigente = (desde <= fecha) & (hasta >= fecha)
    return v.loc[vigente, ["date", "value"]]

for f in ["2025-04-10", "2025-05-20", "2026-01-01"]:
    print(f, conocido_el(versiones, f).to_dict("records"))
OUT [04]Qué valor se conocía en cada fecha
2025-04-10 [{'date': '2025-03-01', 'value': 151.0}]
2025-05-20 [{'date': '2025-03-01', 'value': 228.0}]
2026-01-01 [{'date': '2025-03-01', 'value': 205.0}]

El mismo dato de marzo valía 151 para quien lo miraba en abril, 228 en mayo y 205 desde junio. Un backtest que use la serie de hoy está usando en abril un 205 que nadie conocía. Las fechas se comparan como texto porque tienen formato ISO (AAAA-MM-DD), que ordena igual que las fechas; 9999-12-31 marca «vigente todavía». La lección 1.5.2 trabaja esto con datos macro completos.

Un aviso de licencia: FRED reúne series de muchos organismos, y las que llevan aviso de copyright son de terceros con restricciones propias; para usarlas más allá del uso personal hay que pedir permiso a su dueño3Nota 3 · FRED: Legal Notices, Information and Disclaimers y FRED API Terms of Use..

SEC EDGAR: fundamentales y documentos de EE. UU.

EDGAR es el sistema de la SEC donde las empresas cotizadas de EE. UU. presentan sus informes (10-K, 10-Q, 8-K…). Sus APIs JSON en data.sec.gov no piden clave ni autenticación4Nota 4 · SEC: EDGAR Application Programming Interfaces.; las dos más útiles:

  • https://data.sec.gov/submissions/CIK##########.json: el historial de presentaciones de una empresa.
  • https://data.sec.gov/api/xbrl/companyfacts/CIK##########.json: todos los datos contables XBRL de la empresa.

########## es el CIK (identificador central de la SEC) con diez dígitos, ceros a la izquierda incluidos. A cambio de no pedir clave, la SEC fija reglas de acceso justo5Nota 5 · SEC: Accessing EDGAR Data, consultada el 21/10/2026.: un máximo actual de 10 peticiones por segundo y declarar un User-Agent con el nombre de quien pide y un correo de contacto, con este formato de ejemplo: User-Agent: Sample Company Name AdminContact@<sample company domain>.com. Las peticiones sin él pueden recibir un 403.

IN [05]python
def url_submissions(cik):
    return f"https://data.sec.gov/submissions/CIK{int(cik):010d}.json"

CABECERAS = {
    "User-Agent": "Mi Proyecto de Research [email protected]",
    "Accept-Encoding": "gzip, deflate",
}
print(url_submissions(320193))
print(url_submissions("0000320193"))
OUT [05]CIK con diez dígitos y cabeceras declaradas
https://data.sec.gov/submissions/CIK0000320193.json
https://data.sec.gov/submissions/CIK0000320193.json
IN [06]edgar_submissions.pypython
import gzip
import json
from urllib.request import Request, urlopen

req = Request(url_submissions(320193), headers=CABECERAS)
with urlopen(req, timeout=10) as r:
    crudo = r.read()
    if r.headers.get("Content-Encoding") == "gzip":
        crudo = gzip.decompress(crudo)
sub = json.loads(crudo)

La respuesta de submissions tiene datos de la empresa (cik, name, tickers, exchanges…) y, en filings.recent, las presentaciones recientes en formato por columnas: un diccionario de listas paralelas (accessionNumber, filingDate, reportDate, form, primaryDocument…), no una lista de registros. Es justo lo que espera pd.DataFrame. Con una muestra ilustrativa de esa estructura, de una empresa ficticia:

IN [07]python
sub = {
    "cik": "0000000042", "name": "NOVA CORP", "tickers": ["NOVA"],
    "filings": {"recent": {
        "accessionNumber": ["0000000042-25-000031",
                            "0000000042-25-000027",
                            "0000000042-25-000012"],
        "filingDate": ["2025-11-05", "2025-10-24", "2025-08-06"],
        "reportDate": ["2025-09-30", "", "2025-06-30"],
        "form": ["10-Q", "8-K", "10-Q"],
        "primaryDocument": ["nova-10q.htm", "nova-8k.htm",
                            "nova-10q.htm"],
    }},
}
pres = pd.DataFrame(sub["filings"]["recent"])
pres["filingDate"] = pd.to_datetime(pres["filingDate"])
pres[pres["form"].isin(["10-K", "10-Q"])][
    ["form", "reportDate", "filingDate"]]
OUT [07]Presentaciones de una empresa, de columnas a filas
   form  reportDate filingDate
0  10-Q  2025-09-30 2025-11-05
2  10-Q  2025-06-30 2025-08-06

Las dos fechas son la clave para usar fundamentales sin anticipación: reportDate es el final del periodo (30 de septiembre) y filingDate, el día en que el informe se hizo público (5 de noviembre). Un backtest solo puede usar las cifras del trimestre desde la segunda (lección 1.5.1).

Respetar el ritmo de EDGAR

Diez peticiones por segundo parecen muchas hasta que se descargan miles de documentos en un bucle. El bucle más simple dispara las peticiones tan rápido como responde la red y supera el límite en el primer segundo. La solución es un limitador de ritmo que espera lo necesario entre peticiones. Para ejecutarlo sin red, con un reloj virtual en milisegundos enteros (así no hay errores de redondeo en las comparaciones) en el que cada petición tarda 20 ms en responder:

sínoPetición¿10 en 1 s?Esperardata.sec.gov
Fuente Mermaid
flowchart LR
  P[Petición] --> L{¿10 en 1 s?}
  L -- sí --> W[Esperar]
  W --> L
  L -- no --> S[data.sec.gov]
IN [08]python
from collections import deque

def simular(n, por_segundo=None, latencia_ms=20):
    t, hechas, horas = 0, deque(), []      # milisegundos enteros
    for _ in range(n):
        if por_segundo is not None:
            while hechas and t - hechas[0] >= 1000:
                hechas.popleft()
            if len(hechas) >= por_segundo:
                t = hechas[0] + 1000
                hechas.popleft()
        hechas.append(t)
        horas.append(t)
        t += latencia_ms
    return pd.Series(horas)

def peor_segundo(ms):
    return max(((ms >= h) & (ms < h + 1000)).sum() for h in ms)

sin_limite, con_limite = simular(100), simular(100, por_segundo=10)
print("sin limitador:", f"{sin_limite.iloc[-1] / 1000:.2f} s,",
      "máximo en un segundo:", peor_segundo(sin_limite))
print("con limitador:", f"{con_limite.iloc[-1] / 1000:.2f} s,",
      "máximo en un segundo:", peor_segundo(con_limite))
OUT [08]Limitador de 10 peticiones por segundo, en un reloj virtual
sin limitador: 1.98 s, máximo en un segundo: 50
con limitador: 9.18 s, máximo en un segundo: 10
Fig. — Cien peticiones a EDGAR en un reloj virtual, con y sin limitador de 10 peticiones por segundo (respuesta de 20 ms)

Sin limitador, las 100 peticiones salen en 2 segundos y en el peor segundo hay 50: cinco veces el máximo. Con el limitador tardan 9,18 segundos y nunca hay más de 10 en ninguna ventana de un segundo. Es más lento, pero es la diferencia entre una descarga que termina y una IP bloqueada. La lección 1.4.1 generaliza el limitador y le añade reintentos.

BCE y Banco de España: datos europeos sin clave

El Portal de Datos del BCE ofrece un servicio web SDMX 2.1 con punto de entrada en data-api.ecb.europa.eu/service/. Cada serie se identifica por una clave que une los valores de sus dimensiones con puntos: el tipo de cambio de referencia diario del dólar frente al euro es EXR/D.USD.EUR.SP00.A (conjunto EXR; frecuencia D, divisa USD, divisa de referencia EUR, tipo SP00, variante A). Admite csvdata y jsondata, entre otros formatos, y parámetros como startPeriod o lastNObservations6Nota 6 · ECB Data Portal: API, Overview y Data.. No pide clave.

IN [09]bce_exr.pypython
url = ("https://data-api.ecb.europa.eu/service/data/EXR/"
       "D.USD.EUR.SP00.A?lastNObservations=3&format=csvdata")
bce = pd.read_csv(url)

La respuesta CSV trae una fila por observación y más de treinta columnas: las dimensiones de la clave, la fecha (TIME_PERIOD), el valor (OBS_VALUE) y muchos metadatos (estado de la observación, título, unidad, decimales…). Una muestra con las columnas principales y los tres valores que devolvía la consulta anterior el 21/10/2026:

IN [10]python
from io import StringIO

bce_csv = """KEY,FREQ,CURRENCY,TIME_PERIOD,OBS_VALUE,OBS_STATUS,TITLE
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-16,1.1253,A,US dollar/Euro
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-19,1.1225,A,US dollar/Euro
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-20,1.1215,A,US dollar/Euro
"""
bce = pd.read_csv(StringIO(bce_csv), parse_dates=["TIME_PERIOD"])
eurusd = bce.set_index("TIME_PERIOD")["OBS_VALUE"].rename("EURUSD")
print(eurusd)
print(f"Variación: {eurusd.pct_change().iloc[-1]:.3%}")
OUT [10]CSV del BCE: quedarse con lo necesario
TIME_PERIOD
2026-10-16    1.1253
2026-10-19    1.1225
2026-10-20    1.1215
Name: EURUSD, dtype: float64
Variación: -0.089%

El tipo de referencia del BCE es un dato del día (el propio título de la serie lo sitúa a las 14:15 CET), no un precio negociable: sirve para valorar, para convertir divisas en un informe o para macro, no para un sistema intradía de divisas (lección 1.2.4). Su reutilización es gratuita con dos condiciones: citar la fuente y no modificar las estadísticas ni sus metadatos; los datos de terceros que publique el BCE necesitan permiso de quien los elabora7Nota 7 · BCE: Policy regarding the reuse of ESCB statistics..

El Banco de España tiene un servicio web JSON que da acceso a toda la información de su sección de estadísticas y de la aplicación HERMES, con dos tipos de petición: el último dato de una o varias series (favoritas) y un listado de la serie en un rango (listaSeries). Para descargas masivas ofrece también todos los CSV de series temporales en un ZIP. La serie D_1NBAF472 es el Euribor a un año8Nota 8 · Banco de España: servicio web de Estadísticas (API) y descargas completas, consultadas el 21/10/2026.:

IN [11]bde_ultimo_dato.pypython
url = ("https://app.bde.es/bierest/resources/srdatosapp/favoritas"
       "?idioma=es&series=D_1NBAF472")
IN [12]python
bde_txt = """[{"serie": "D_1NBAF472",
  "descripcionCorta": "Euribor a un año", "codFrecuencia": "M",
  "decimales": 3, "simbolo": "%", "tendencia": "+",
  "fechaValor": "2026-09-01T08:15:00Z", "valor": 3.247}]"""
bde = pd.DataFrame(json.loads(bde_txt))
bde["fechaValor"] = pd.to_datetime(bde["fechaValor"])
bde[["serie", "descripcionCorta", "fechaValor", "valor", "simbolo"]]
OUT [12]Último dato del Banco de España
        serie  descripcionCorta                fechaValor  valor simbolo
0  D_1NBAF472  Euribor a un año 2026-09-01 08:15:00+00:00  3.247       %

Es la respuesta que devolvía el servicio el 21/10/2026: el dato mensual de septiembre de 2026, un 3,247 %. La marca fechaValor va en UTC (Z) y la frecuencia es mensual (M): conviene guardarlas, porque una serie mensual unida a precios diarios necesita una regla de alineación explícita (lección 1.4.2).

CNMV: información regulatoria

La CNMV no tiene una API pública documentada como las anteriores, pero publica información útil en su web, en consultas y archivos descargables9Nota 9 · CNMV: posiciones cortas, otra información relevante, registros oficiales y estadísticas.:

  • Posiciones cortas: las notificaciones de posiciones cortas netas sobre acciones españolas, con un fichero descargable de las publicadas e históricas. Útil para estudiar presión bajista o riesgo de short squeeze.
  • Otra información relevante: las comunicaciones de los emisores (antes «hechos relevantes», hasta febrero de 2020), notificaciones de directivos y participaciones significativas.
  • Registros oficiales de emisores y cotizadas, y estadísticas de emisores, fondos y empresas de servicios de inversión.

Como no hay API, el acceso es por descarga de archivos o lectura de páginas, y cualquier automatización tiene que respetar las condiciones de uso de la web. Es información para research y para contexto, no un feed.

Un formato común para fuentes distintas

Cada fuente devuelve un formato distinto. Antes de guardar, conviene llevar todo a una forma común, con la fuente y la serie como columnas, para que el resto del código no dependa de quién dio cada dato:

IN [13]python
largo = pd.concat([
    pd.DataFrame({"fuente": "FRED", "serie": "DGS10",
                  "fecha": obs["date"], "valor": obs["value"]}),
    pd.DataFrame({"fuente": "BCE", "serie": "EXR.D.USD.EUR",
                  "fecha": eurusd.index, "valor": eurusd.values}),
    pd.DataFrame({"fuente": "BdE", "serie": bde["serie"],
                  "fecha": bde["fechaValor"].dt.tz_localize(None)
                  .dt.normalize(), "valor": bde["valor"]}),
], ignore_index=True)
print(largo.dtypes.to_dict())
largo.groupby(["fuente", "serie"])["fecha"].agg(["min", "max", "size"])
OUT [13]Las tres series en un formato largo común
{'fuente': <StringDtype(na_value=nan)>, 'serie': <StringDtype(na_value=nan)>, 'fecha': dtype('<M8[us]'), 'valor': dtype('float64')}
                            min        max  size
fuente serie
BCE    EXR.D.USD.EUR 2026-10-16 2026-10-20     3
BdE    D_1NBAF472    2026-09-01 2026-09-01     1
FRED   DGS10         2025-01-02 2025-01-06     3

El formato largo (una fila por fuente, serie y fecha) es fácil de guardar en Parquet, de filtrar y de pivotar cuando hace falta (lección 1.4.7). Lo que se pierde al normalizar (las unidades, la frecuencia, el estado de cada observación) se guarda aparte, en una tabla de metadatos por serie: sin ella, dentro de un año nadie sabrá si 3.247 eran puntos porcentuales o tanto por uno.

Ejercicios

  1. Añade a versiones una cuarta publicación del dato de marzo, 199,0, vigente desde el 2025-09-05. Ajusta el realtime_end de la anterior y comprueba qué valor se conocía el 2025-07-01 y el 2025-10-01.
Solución
IN [14]python
v2 = versiones.copy()
v2.loc[2, "realtime_end"] = "2025-09-04"
v2.loc[3] = ["2025-03-01", 199.0, "2025-09-05", "9999-12-31"]
for f in ["2025-07-01", "2025-10-01"]:
    print(f, conocido_el(v2, f)["value"].tolist())
OUT [14]Una revisión más
2025-07-01 [205.0]
2025-10-01 [199.0]

El 1 de julio seguía vigente 205; desde el 5 de septiembre, 199. Cada revisión cierra el periodo de la anterior: si se olvida ajustar realtime_end, dos versiones parecen vigentes a la vez y conocido_el devuelve dos valores.

  1. Con simular, ¿cuántas peticiones por segundo como máximo saldrían si cada respuesta de EDGAR tardase 150 ms y no hubiera limitador? ¿Hace falta entonces el limitador?
Solución
IN [15]python
lentas = simular(100, latencia_ms=150)
print("máximo en un segundo:", peor_segundo(lentas))
OUT [15]Con respuestas más lentas
máximo en un segundo: 7

Con 150 ms por respuesta, un bucle secuencial no pasa de 7 peticiones por segundo: por debajo del límite. Pero el limitador sigue haciendo falta: la latencia cambia (una caché, otra red) y, en cuanto el código se paraleliza con hilos, la cuenta se multiplica. El límite se impone en el código, no se confía a la lentitud de la red.

  1. Vas a estudiar si el diferencial entre el Euribor a un año y el bono a 10 años de EE. UU. anticipa algo en el EURUSD. Di de qué fuentes sacarías cada serie, en qué frecuencia vienen y qué tendrías que resolver para unirlas.
Solución

Euribor a un año: Banco de España (o el BCE), mensual. Rentabilidad del bono a 10 años de EE. UU.: FRED, diaria. EURUSD: el tipo de referencia diario del BCE (EXR), un dato de cierre a media tarde de Fráncfort. Para unirlas hay que decidir la frecuencia común (mensual, si el Euribor es mensual), qué valor diario representa cada mes (el último conocido al cierre de mes, no el promedio que solo se sabe después), las horas de cada dato (el tipo del BCE se fija antes del cierre de Nueva York) y, si se usan datos que se revisan, la versión que se conocía en cada fecha (vintages de FRED). Además, citar las fuentes y comprobar las condiciones de uso de cada serie (lección 1.3.7).

Footnotes

  1. README de yfinance y documentación, consultados el 21/10/2026; versión 1.7.0 en PyPI. ↩

  2. FRED API: series/observations, API key y errores («Up to 120 requests per minute»), consultados el 21/10/2026. ↩

  3. FRED: Legal Notices, Information and Disclaimers y FRED API Terms of Use. ↩

  4. SEC: EDGAR Application Programming Interfaces. ↩

  5. SEC: Accessing EDGAR Data, consultada el 21/10/2026. ↩

  6. ECB Data Portal: API, Overview y Data. ↩

  7. BCE: Policy regarding the reuse of ESCB statistics. ↩

  8. Banco de España: servicio web de Estadísticas (API) y descargas completas, consultadas el 21/10/2026. ↩

  9. CNMV: posiciones cortas, otra información relevante, registros oficiales y estadísticas. ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. ¿Qué dice el propio README de yfinance sobre su relación con Yahoo?

    • AQue es la API oficial de Yahoo Finance
    • BQue no está afiliada, respaldada ni revisada por Yahoo, que es para investigación y educación y que hay que consultar los términos de Yahoo sobre el uso de los datos
    • CQue los datos son de dominio público
    • DQue tiene un acuerdo comercial con Yahoo
    Ver respuesta

    Respuesta B. Que no está afiliada, respaldada ni revisada por Yahoo, que es para investigación y educación y que hay que consultar los términos de Yahoo sobre el uso de los datos

    yfinance es una herramienta de código abierto que usa las APIs públicas de Yahoo. El README remite a los términos de uso de Yahoo y recuerda que su API es para uso personal.

  2. ¿Qué exige la SEC para descargar de EDGAR de forma automatizada?

    • AUna clave de API de pago
    • BDeclarar un User-Agent con nombre y correo de contacto y no pasar de 10 peticiones por segundo
    • CRegistrarse como profesional
    • DNada
    Ver respuesta

    Respuesta B. Declarar un User-Agent con nombre y correo de contacto y no pasar de 10 peticiones por segundo

    La página «Accessing EDGAR Data» fija un máximo actual de 10 peticiones por segundo y pide declarar el User-Agent. Las APIs de data.sec.gov no necesitan clave.

  3. En la API de FRED, el campo value de cada observación llega como "4.33". ¿Qué hay que hacer?

    • ANada, ya es un número
    • BConvertirlo con pd.to_numeric(..., errors="coerce"), porque es texto y puede haber valores no numéricos
    • CMultiplicarlo por 100
    • DLeerlo como fecha
    Ver respuesta

    Respuesta B. Convertirlo con pd.to_numeric(..., errors="coerce"), porque es texto y puede haber valores no numéricos

    El JSON de FRED trae los valores entre comillas. Si no se convierten, las operaciones aritméticas fallan o concatenan texto.

  4. ¿Qué permite el parámetro realtime_start / realtime_end de FRED?

    • APedir datos en tiempo real
    • BPedir los datos tal como se conocían en una fecha pasada, con las revisiones posteriores excluidas (ALFRED)
    • CCambiar la zona horaria
    • DLimitar el número de observaciones
    Ver respuesta

    Respuesta B. Pedir los datos tal como se conocían en una fecha pasada, con las revisiones posteriores excluidas (ALFRED)

    Las series macro se revisan. Con el periodo de tiempo real (o vintage_dates) se obtienen las versiones que existían en cada fecha, que es lo que necesita un backtest sin anticipación (lección 1.5.2).

  5. ¿Qué condición pone el BCE para reutilizar sus estadísticas públicas?

    • APagar una licencia
    • BCitar la fuente y no modificar las estadísticas (ni sus metadatos); los datos de terceros necesitan permiso de su autor
    • CUso solo no comercial
    • DNinguna
    Ver respuesta

    Respuesta B. Citar la fuente y no modificar las estadísticas (ni sus metadatos); los datos de terceros necesitan permiso de su autor

    La política de reutilización de estadísticas del SEBC permite la reutilización gratuita citando la fuente («Source: ECB statistics.») y sin modificar los datos.

Ideas clave

  • «Gratis» incluye dos cosas distintas: servicios oficiales con API documentada (FRED, EDGAR, BCE, Banco de España, CNMV) y herramientas no oficiales que leen una web (yfinance).
  • yfinance no está afiliada a Yahoo, ajusta precios por defecto (auto_adjust=True) y puede devolver errores de límite (YFRateLimitError): sirve para aprender, no como base de un sistema.
  • Cada fuente oficial tiene sus reglas: FRED pide clave y admite hasta 120 peticiones por minuto; EDGAR, User-Agent declarado y 10 por segundo; el BCE, citar la fuente.
  • Las respuestas hay que tiparlas: valores como texto en FRED, listas en columnas en EDGAR, decenas de columnas de metadatos en el CSV del BCE.
  • Las fuentes cambian: la descarga directa de CSV de Stooq, muy usada durante años, devolvía el 21/10/2026 una verificación de navegador en lugar del archivo.

Recursos