Lección 1.3.3 · 55 min

APIs de datos retail y prosumer

Entre las fuentes gratuitas y los proveedores institucionales hay un escalón de APIs con planes de decenas de dólares al mes. Esta lección compara siete con sus propias páginas, lee el formato de una respuesta real y escribe un cliente que pagina y respeta el límite de peticiones contra un servidor local.

Fig. — Planes de acciones de Massive (antes Polygon.io): años de historia, precio al mes y retraso de los datos, según su página de precios a fecha de 21/10/2026
03/08
Módulo
1.3 · Fuentes de datos de mercado
Duración
55 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

El escalón entre lo gratuito y lo institucional

Las fuentes de la lección 1.3.2 son gratuitas, pero no oficiales (yfinance) o no cubren el mercado al detalle (FRED, EDGAR, bancos centrales). Los proveedores de la lección 1.3.4 cubren todo, a precio institucional. En medio hay un grupo de empresas que venden APIs de datos a particulares, a desarrolladores y a pequeñas empresas: el mercado retail y prosumer. Tienen tres cosas en común:

  • Un plan gratuito, con historia corta o pocas peticiones, para probar.
  • Planes de pago de decenas de dólares al mes, que suben historia, quitan límites y reducen el retraso.
  • Una API REST documentada, a veces websockets para tiempo real, y un cliente oficial en Python.

Esta lección compara siete con lo que dicen sus propias páginas. Todos los precios y límites que siguen están leídos en la página de cada proveedor a fecha de 21/10/2026. Cambian a menudo: antes de pagar hay que volver a leerlos.

Massive, antes Polygon.io

Polygon.io cambió su nombre a Massive el 30 de octubre de 2025. Según el anuncio, la API antigua (api.polygon.io) sigue funcionando «durante un periodo prolongado» en paralelo con la nueva (api.massive.com), y las páginas antiguas redirigen a las nuevas1Nota 1 · Massive, «Polygon.io is Now Massive», 30/10/2025, y página de precios, consultadas el 21/10/2026.. El cliente oficial de Python se publica ahora como massive; el paquete antiguo, polygon-api-client, sigue en PyPI pero su última versión es del día del cambio de nombre.

Sus planes de acciones son una escalera clara:

PlanPrecio al mesHistoriaRetrasoPeticiones
Stocks Basic0 USD2 añosFin de día5 por minuto
Stocks Starter29 USD5 años15 minutosIlimitadas
Stocks Developer79 USD10 años15 minutosIlimitadas; añade operaciones
Stocks Advanced199 USD20+ añosTiempo realIlimitadas; añade cotizaciones

La figura de la cabecera resume los cuatro planes. Precios con facturación mensual; la página ofrece un 20 % de descuento con el plan anual. El tiempo real solo está en Advanced, y la página lo marca como para usuarios no profesionales («Non-pros only»): la diferencia entre profesional y no profesional es la de las licencias de las bolsas (lección 1.3.7). Lo que suben los planes no es solo la historia: Developer añade las operaciones (tick a tick) y Advanced, las cotizaciones (bid y ask), que son los datos de las lecciones 1.1.3 y 1.3.1.

Los demás, plan por plan

ProveedorPlan gratuitoPlan de pago más barato con acciones de EE. UU.Detalle
AlpacaBasic: tiempo real solo de IEX, histórico desde 2016 sin los últimos 15 minutos, 200 llamadas por minutoAlgo Trader Plus: 99 USD al mes, todas las bolsas de EE. UU., 10.000 llamadas por minutoEl bróker da los datos con la cuenta; paginación con next_page_token
TiingoStarter: 50 peticiones por hora, 1.000 al día, 500 símbolos al mesPower: 30 USD al mes (300 al año) para particulares; 50 USD para uso comercial internoFin de día con más de 30 años, con precios ajustados, dividendos y splits en la misma respuesta
EODHD20 llamadas al día, 1 año de historiaEOD Historical All-World: 19,99 USD al mesCobra cada llamada en unidades: 1 por fin de día, 5 por intradía, 10 por fundamentales
Alpha Vantage25 peticiones al día49,99 USD al mes: 75 peticiones por minuto y datos de EE. UU. con 15 minutos de retrasoEl tiempo real y el diferido son de pago
Twelve DataBasic: 8 créditos por minuto, 800 al díaGrow: desde 29 USD al mes (55 créditos por minuto)Facturado al año, más barato
Financial Modeling PrepBasic: 250 llamadas al día, 5 años de fin de díaStarter: 29 USD al mes (19 USD al mes con facturación anual), 300 por minutoFuerte en fundamentales

Fuentes: la página de precios o de planes de cada proveedor, leída el 21/10/20262Nota 2 · Páginas de precios consultadas el 21/10/2026: Alpaca, Tiingo, EODHD, Alpha Vantage, Twelve Data, Financial Modeling Prep.. Los planes no son equivalentes: unos son de fin de día, otros intradía con retraso, otros tiempo real de una sola bolsa. Comparar solo el precio sería comparar cosas distintas.

Fig. — Precio del plan de pago más barato con acciones de EE. UU. de cada proveedor, con facturación mensual, a fecha de 21/10/2026. Los planes no son equivalentes: ver la tabla

Dos detalles que no se ven en la tabla y que importan:

  • Qué bolsa da el «tiempo real». El plan gratuito de Alpaca da tiempo real solo de IEX, una de las bolsas de EE. UU.: el precio es real, pero el volumen es una fracción del total y el mejor precio puede no ser el del mercado consolidado (lección 1.1.1). Para el consolidado hace falta el plan de pago.
  • Créditos, no peticiones. EODHD y Twelve Data cuentan créditos, y no todas las llamadas cuestan lo mismo. Un límite de «100.000 llamadas al día» se agota antes si cada llamada intradía cuesta cinco.

Comparar límites en la misma unidad

Los planes gratuitos dan sus límites en unidades distintas. Para compararlos hay que pasarlos a una sola, por ejemplo peticiones al día, sabiendo que es un techo teórico: nadie hace peticiones sin parar las 24 horas.

IN [01]python
import math

import pandas as pd

limites = pd.DataFrame([
    ("Massive Basic", 5, "minuto"),
    ("Alpaca Basic", 200, "minuto"),
    ("Tiingo Starter", 1000, "dia"),
    ("Twelve Data Basic", 800, "dia"),
    ("FMP Basic", 250, "dia"),
    ("Alpha Vantage", 25, "dia"),
    ("EODHD gratuito", 20, "dia"),
], columns=["plan", "limite", "por"]).set_index("plan")

por_dia = {"minuto": 24 * 60, "hora": 24, "dia": 1}
limites["al_dia"] = limites["limite"] * limites["por"].map(por_dia)
limites.sort_values("al_dia", ascending=False)
OUT [01]Límites de los planes gratuitos, en peticiones al día
                   limite     por  al_dia
plan
Alpaca Basic          200  minuto  288000
Massive Basic           5  minuto    7200
Tiingo Starter       1000     dia    1000
Twelve Data Basic     800     dia     800
FMP Basic             250     dia     250
Alpha Vantage          25     dia      25
EODHD gratuito         20     dia      20

Tiingo tiene dos límites, uno por hora (50) y otro por día (1.000); aquí cuenta el más restrictivo de los dos, el diario, porque 50 por hora durante 24 horas serían 1.200. Twelve Data también tiene dos (8 por minuto y 800 al día). Con este techo ya se puede estimar cuánto tarda la descarga que importa:

IN [02]python
acciones, por_peticion = 500, 1   # una petición por acción
peticiones = acciones * por_peticion
limites["dias"] = (peticiones / limites["al_dia"]).apply(math.ceil)
limites[["al_dia", "dias"]].sort_values("dias")
OUT [02]Días para descargar el histórico diario de 500 acciones
                   al_dia  dias
plan
Massive Basic        7200     1
Alpaca Basic       288000     1
Tiingo Starter       1000     1
Twelve Data Basic     800     1
FMP Basic             250     2
Alpha Vantage          25    20
EODHD gratuito         20    25
Fig. — Límite de los planes gratuitos pasado a peticiones al día (techo teórico), a fecha de 21/10/2026

Con Massive, Alpaca, Tiingo o Twelve Data, el histórico diario de 500 acciones cabe en un día (en Massive, a 5 por minuto, son 100 minutos); con FMP, en dos. Con Alpha Vantage o EODHD hacen falta 20 o 25 días. Eso no hace inútiles a estos planes: obliga a diseñar la descarga para que guarde lo que lleva y continúe al día siguiente sin repetir (lección 1.4.8). Y queda la otra mitad del problema: el plan Basic de Massive solo tiene 2 años de historia, y el gratuito de EODHD, 1. El límite dice cuánto tardas; la historia, si lo que necesitas existe en ese plan.

Anatomía de una respuesta de barras

El endpoint de barras de Massive es /v2/aggs/ticker/{ticker}/range/{multiplicador}/{intervalo}/{desde}/{hasta}. La respuesta lleva metadatos (status, request_id, resultsCount, adjusted), la lista results con una barra por elemento y, si hay más páginas, next_url. Cada barra trae o, h, l, c (precios), v (volumen), vw (precio medio ponderado por volumen), n (número de operaciones) y t, el inicio de la barra en milisegundos Unix; limit admite hasta 50.000 barras por petición, con 5.000 por defecto3Nota 3 · Massive, Custom Bars (OHLC) y REST API Quickstart, consultadas el 21/10/2026..

Una muestra ilustrativa con esa estructura (los números son de NOVA, la acción ficticia del curso):

IN [03]python
import json

texto = """{
  "ticker": "NOVA", "adjusted": false, "queryCount": 2,
  "resultsCount": 2, "status": "OK", "request_id": "ej-0001",
  "results": [
    {"t": 1748836800000, "o": 53.35, "h": 53.94, "l": 52.55,
     "c": 53.12, "v": 8888714, "vw": 53.21, "n": 50946},
    {"t": 1748923200000, "o": 53.05, "h": 53.4, "l": 52.6,
     "c": 52.92, "v": 6151230, "vw": 52.98, "n": 41210}
  ],
  "next_url": "https://api.massive.com/v2/aggs/...?cursor=ej"
}"""
respuesta = json.loads(texto)
barras = pd.DataFrame(respuesta["results"])
barras["t"] = pd.to_datetime(barras["t"], unit="ms", utc=True)
print(respuesta["status"], respuesta["resultsCount"],
      "next_url" in respuesta)
barras.set_index("t")
OUT [03]Una respuesta con el esquema documentado
OK 2 True
                               o      h      l      c        v     vw      n
t
2025-06-02 04:00:00+00:00  53.35  53.94  52.55  53.12  8888714  53.21  50946
2025-06-03 04:00:00+00:00  53.05  53.40  52.60  52.92  6151230  52.98  41210

Dos cosas que conviene notar. La marca de una barra diaria es la medianoche de Nueva York expresada en UTC: las 04:00 en verano y las 05:00 en invierno, como en el ejemplo de la documentación. En una barra diaria, la fecha UTC coincide con la de Nueva York; en barras intradía no: una barra de las 21:00 de Nueva York ya es del día siguiente en UTC. Por eso se convierte a la zona del mercado (.tz_convert("America/New_York")) antes de quedarse con la fecha. Y adjusted dice si los precios están ajustados por splits; la lección 1.3.8 explica por qué hay que saberlo siempre.

Un cliente que pagina y respeta el límite

Un cliente REST para estas APIs tiene que hacer tres cosas bien: autenticarse sin dejar la clave en el código, seguir la paginación hasta el final y reaccionar a los errores de límite (HTTP 429) esperando lo que diga el servidor.

429200sínoURL inicialGET con clave¿Código?Retry-AfterGuardar results¿next_url?Fin
Fuente Mermaid
flowchart TB
  U[URL inicial] --> G[GET con clave]
  G --> C{¿Código?}
  C -- 429 --> W[Retry-After]
  W --> G
  C -- 200 --> R[Guardar results]
  R --> N{¿next_url?}
  N -- sí --> G
  N -- no --> F[Fin]

Para ejecutarlo de verdad sin red ni clave, la celda siguiente levanta un servidor local en un hilo que imita la API: sirve las 502 sesiones de NOVA de 2024 y 2025 en el formato de la sección anterior, en páginas de limit barras con un cursor en next_url, exige la clave en apiKey y responde 429 con Retry-After: 1 si recibe más de 5 peticiones en un segundo.

IN [04]python
import threading
import time
from collections import deque
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse

nova = pd.read_csv("nova_diario_sin_ajustar.csv",
                   parse_dates=["fecha"])
nova = nova[nova["fecha"] >= "2024-01-01"]
abre_ny = (nova["fecha"].dt.tz_localize("America/New_York")
           .dt.tz_convert("UTC"))
BARRAS = [
    {"t": int(ts.timestamp() * 1000), "o": f.apertura,
     "h": f.maximo, "l": f.minimo, "c": f.cierre, "v": f.volumen}
    for ts, f in zip(abre_ny, nova.itertuples())
]

class ApiFalsa(BaseHTTPRequestHandler):
    recientes = deque()          # horas de las peticiones aceptadas
    contador = {"200": 0, "401": 0, "429": 0}

    def responder(self, codigo, cuerpo, cabeceras=()):
        self.contador[str(codigo)] += 1
        self.send_response(codigo)
        self.send_header("Content-Type", "application/json")
        for k, v in cabeceras:
            self.send_header(k, v)
        self.end_headers()
        self.wfile.write(json.dumps(cuerpo).encode())

    def do_GET(self):
        q = parse_qs(urlparse(self.path).query)
        if q.get("apiKey") != ["clave-de-prueba"]:
            return self.responder(401, {"status": "ERROR"})
        ahora = time.monotonic()
        while self.recientes and ahora - self.recientes[0] > 1:
            self.recientes.popleft()
        if len(self.recientes) >= 5:
            return self.responder(429, {"status": "ERROR"},
                                  [("Retry-After", "1")])
        self.recientes.append(ahora)
        inicio = int(q.get("cursor", ["0"])[0])
        limite = int(q.get("limit", ["50"])[0])
        pagina = BARRAS[inicio:inicio + limite]
        cuerpo = {"status": "OK", "resultsCount": len(pagina),
                  "results": pagina}
        if inicio + limite < len(BARRAS):
            base = f"http://127.0.0.1:{self.server.server_port}"
            cuerpo["next_url"] = (f"{base}/v2/aggs/ticker/NOVA?"
                                  f"cursor={inicio + limite}"
                                  f"&limit={limite}")
        self.responder(200, cuerpo)

    def log_message(self, *args):    # sin registro en la salida
        pass

servidor = ThreadingHTTPServer(("127.0.0.1", 0), ApiFalsa)
threading.Thread(target=servidor.serve_forever, daemon=True).start()
URL = f"http://127.0.0.1:{servidor.server_port}/v2/aggs/ticker/NOVA"
print(len(BARRAS), "barras servidas")
OUT [04]Servidor local que imita la API
502 barras servidas

El puerto 0 pide al sistema uno libre, así que la celda no choca con nada que ya esté escuchando. Ahora el cliente, con la biblioteca estándar (urllib): sin la clave, el servidor responde 401 y urlopen lanza una excepción.

IN [05]python
from urllib.error import HTTPError
from urllib.request import urlopen

urlopen(f"{URL}?limit=50", timeout=5)
OUT [05]Sin clave: 401
urllib.error.HTTPError: HTTP Error 401: Unauthorized

La clave se lee de una variable de entorno. En la celda se fija a mano para el servidor local; en un programa real estaría en el entorno o en un .env fuera del repositorio (lección 0.2.4):

IN [06]python
import os
from urllib.parse import urlencode

os.environ.setdefault("API_KEY", "clave-de-prueba")  # solo aquí

def get_json(url, intentos=5):
    sep = "&" if "?" in url else "?"
    clave = urlencode({"apiKey": os.environ["API_KEY"]})
    firmada = f"{url}{sep}{clave}"
    for _ in range(intentos):
        try:
            with urlopen(firmada, timeout=5) as r:
                return json.load(r)
        except HTTPError as e:
            if e.code != 429:
                raise
            time.sleep(float(e.headers.get("Retry-After", "1")))
    raise RuntimeError(f"sigue en 429 tras {intentos} intentos")

def descargar_todo(url):
    filas, paginas = [], 0
    while url:
        datos = get_json(url)
        filas += datos["results"]
        paginas += 1
        url = datos.get("next_url")
    return filas, paginas

filas, paginas = descargar_todo(f"{URL}?limit=50")
print(len(filas), "barras en", paginas, "páginas;",
      ApiFalsa.contador["429"], "respuestas 429")
OUT [06]Cliente con paginación y 429
502 barras en 11 páginas; 2 respuestas 429

502 barras en 11 páginas de 50 (la última, de 2). El servidor solo acepta 5 peticiones por segundo, así que el cliente recibe dos 429 por el camino: tras la quinta página y tras la décima. En cada uno espera el segundo que pide Retry-After y repite la misma petición; no salta de página ni empieza de cero. Si el servidor siguiera en 429 tras varios intentos, el cliente se rinde con un error claro en lugar de quedarse en un bucle. La lección 1.4.1 añade lo que falta para producción: espera exponencial con aleatoriedad, errores de red y 5xx, y websockets.

IN [07]python
df = pd.DataFrame(filas)
df["t"] = pd.to_datetime(df["t"], unit="ms", utc=True)
print(df["t"].is_unique, df["t"].is_monotonic_increasing)
print(df["t"].iloc[0], "->", df["t"].iloc[-1])
servidor.shutdown()
OUT [07]Las páginas, juntas y sin huecos
True True
2024-01-02 05:00:00+00:00 -> 2025-12-31 05:00:00+00:00

La comprobación final es la de siempre: marcas únicas y crecientes, y la primera y la última donde se esperaban. Una paginación mal hecha suele dejar duplicados en las juntas de las páginas o perder la última.

El cliente oficial

Con el cliente oficial, todo lo anterior queda dentro de la librería: la paginación se recorre con un iterador y la clave va en el constructor. El ejemplo de la documentación de Massive tiene esta forma (necesita red y una clave):

IN [08]massive_barras.pypython
import os

from massive import RESTClient       # uv add massive

client = RESTClient(os.environ["MASSIVE_API_KEY"])
barras = list(client.list_aggs(
    "AAPL", 1, "day", "2025-01-01", "2025-12-31",
    adjusted="true", sort="asc", limit=50000,
))
print(len(barras), barras[0])

Los clientes oficiales ahorran trabajo, pero conviene haber escrito uno a mano una vez: cuando una descarga falla a las tres de la madrugada, hay que saber qué es un 429, qué es un cursor y dónde se perdió la última página.

Cuánto cuesta una descarga intradía

Con el formato y los límites ya se puede presupuestar una descarga grande. Barras de un minuto de la sesión regular de EE. UU. (390 al día, 252 sesiones al año), 500 acciones y 5 años, con un máximo de 50.000 barras por petición:

IN [09]python
barras_ano = 390 * 252
acciones, anos, max_barras = 500, 5, 50_000
por_accion = math.ceil(barras_ano * anos / max_barras)
total = acciones * por_accion
print(f"{barras_ano * anos * acciones:,} barras;"
      f" {por_accion} peticiones por acción; {total:,} en total")
for plan, por_min in [("5 por minuto", 5), ("200 por minuto", 200)]:
    print(f"{plan}: {total / por_min / 60:.1f} horas")
OUT [09]Peticiones y tiempo de una descarga de minutos
245,700,000 barras; 10 peticiones por acción; 5,000 en total
5 por minuto: 16.7 horas
200 por minuto: 0.4 horas

Casi 246 millones de barras caben en 5.000 peticiones, porque cada una trae 50.000 barras. A 5 peticiones por minuto serían casi 17 horas; a 200, 25 minutos. Pero el límite es solo una restricción: el plan Basic de Massive no tiene minutos de 5 años de antigüedad (su historia es de 2), así que la cuenta real empieza por mirar qué plan tiene esos datos. Y queda el almacenamiento: 246 millones de filas en CSV son decenas de gigabytes; en Parquet, bastante menos (lección 1.4.7).

Ejercicios

  1. Repite la estimación de días para el histórico diario de 500 acciones, pero suponiendo que solo puedes usar el 50 % del límite diario (el resto lo necesita tu sistema en producción). ¿Qué planes siguen cabiendo en un día?
Solución
IN [10]python
limites["dias_50"] = (peticiones / (0.5 * limites["al_dia"])
                      ).apply(math.ceil)
limites.loc[limites["dias_50"] == 1].index.tolist()
OUT [10]Con medio límite
['Massive Basic', 'Alpaca Basic', 'Tiingo Starter']

Con la mitad del límite, Massive Basic (3.600 al día), Alpaca Basic y, justo, Tiingo (500 de 500) siguen cabiendo en un día; los demás no. Un sistema en producción consume cuota (cotizaciones, comprobaciones), y una descarga histórica que se la come entera deja al sistema sin datos. Conviene separar claves o planes para las dos cosas.

  1. Modifica el servidor local para que acepte solo 3 peticiones por segundo y descarga de nuevo con limit=50. ¿Cuántos 429 recibe el cliente? ¿Y con limit=200?
Solución
IN [11]python
def probar(maximo, limit):
    ApiFalsa.contador["429"] = 0
    ApiFalsa.recientes.clear()
    original = ApiFalsa.do_GET

    def do_get_estricto(self):
        while len(self.recientes) > maximo - 1:
            if time.monotonic() - self.recientes[0] > 1:
                self.recientes.popleft()
            else:
                return self.responder(429, {}, [("Retry-After", "1")])
        original(self)

    ApiFalsa.do_GET = do_get_estricto
    srv = ThreadingHTTPServer(("127.0.0.1", 0), ApiFalsa)
    threading.Thread(target=srv.serve_forever, daemon=True).start()
    url = f"http://127.0.0.1:{srv.server_port}/v2/aggs/ticker/NOVA"
    filas, paginas = descargar_todo(f"{url}?limit={limit}")
    srv.shutdown()
    ApiFalsa.do_GET = original
    return len(filas), paginas, ApiFalsa.contador["429"]

print(probar(3, 50), probar(3, 200))
OUT [11]Límite más estricto
(502, 11, 3) (502, 3, 0)

Con 3 peticiones por segundo y páginas de 50, el cliente recibe un 429 cada tres páginas; con páginas de 200 bastan 3 peticiones y no recibe ninguno. Pedir páginas grandes (hasta el máximo que admite la API) es la forma más sencilla de gastar menos cuota y de esperar menos.

  1. Tu sistema opera a diario 50 acciones de EE. UU. con datos de cierre y necesita 10 años de historia ajustada. Elige entre los planes de la lección y justifica qué comprobarías antes de pagar.
Solución

Basta con fin de día, así que no hacen falta tiempo real ni intradía. Hacen falta 10 años: descarta el plan Basic de Massive (2 años), Starter (5 años), el gratuito de EODHD (1 año) y Starter de FMP (hasta 5). Quedan, entre otros, los planes de fin de día con historia larga (Tiingo, más de 30 años; EODHD All-World, más de 30) o Developer de Massive (10 años, a un precio mayor). Antes de pagar, con el plan gratuito: si da los precios brutos y los ajustados, y los dividendos y splits (lección 1.3.8); si tiene acciones deslistadas; los límites reales frente a 50 acciones; y la licencia, si el sistema va a publicar algo o a usarse con dinero de terceros (lección 1.3.7).

Footnotes

  1. Massive, «Polygon.io is Now Massive», 30/10/2025, y página de precios, consultadas el 21/10/2026. ↩

  2. Páginas de precios consultadas el 21/10/2026: Alpaca, Tiingo, EODHD, Alpha Vantage, Twelve Data, Financial Modeling Prep. ↩

  3. Massive, Custom Bars (OHLC) y REST API Quickstart, consultadas el 21/10/2026. ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. ¿Qué plan de acciones de Massive da datos en tiempo real, según su página de precios a fecha de 21/10/2026?

    • ABasic (gratuito)
    • BStarter (29 USD al mes)
    • CDeveloper (79 USD al mes)
    • DAdvanced (199 USD al mes)
    Ver respuesta

    Respuesta D. Advanced (199 USD al mes)

    Basic da datos de fin de día; Starter y Developer, con 15 minutos de retraso; solo Advanced da tiempo real (y, según la página, solo para usuarios no profesionales).

  2. Una API responde con next_url en el JSON. ¿Qué debe hacer el cliente?

    • AIgnorarlo si ya tiene datos
    • BPedir esa URL (añadiendo la clave) hasta que la respuesta no traiga next_url
    • CAumentar limit hasta que desaparezca
    • DPedir la misma URL otra vez
    Ver respuesta

    Respuesta B. Pedir esa URL (añadiendo la clave) hasta que la respuesta no traiga next_url

    next_url lleva un cursor con el punto donde sigue la consulta. Si no se sigue, la descarga se queda en la primera página sin ningún error.

  3. El servidor responde 429 con la cabecera Retry-After: 1. ¿Qué es lo correcto?

    • AReintentar inmediatamente en bucle
    • BEsperar al menos un segundo y repetir la misma petición
    • CPasar a la página siguiente
    • DAbortar la descarga
    Ver respuesta

    Respuesta B. Esperar al menos un segundo y repetir la misma petición

    429 significa demasiadas peticiones. Retry-After dice cuánto esperar. Reintentar en bucle solo alarga el bloqueo; saltar de página pierde datos.

  4. Con el plan gratuito de Alpha Vantage (25 peticiones al día), ¿cuántos días tardas en bajar el histórico diario de 500 acciones si cada una cabe en una petición?

    • AUno
    • BCinco
    • CVeinte
    • DCien
    Ver respuesta

    Respuesta C. Veinte

    500 peticiones entre 25 al día son 20 días. Un límite diario cambia la arquitectura: hace falta guardar lo descargado y continuar al día siguiente (lección 1.4.8).

  5. ¿Por qué la clave de la API no debe escribirse en el código?

    • APorque ocupa mucho
    • BPorque el código acaba en un repositorio, una libreta compartida o un traceback, y la clave da acceso a tu cuenta y a tu cuota
    • CPorque las APIs no aceptan claves en el código
    • DPorque cambia cada día
    Ver respuesta

    Respuesta B. Porque el código acaba en un repositorio, una libreta compartida o un traceback, y la clave da acceso a tu cuenta y a tu cuota

    Se lee de una variable de entorno o de un archivo fuera del control de versiones (lección 0.2.4). Una clave publicada hay que revocarla.

Ideas clave

  • El escalón retail y prosumer cuesta de cero a unas decenas de dólares al mes; lo que cambia de un plan a otro es la historia, el retraso (fin de día, 15 minutos, tiempo real) y el límite de peticiones.
  • Polygon.io se llama Massive desde el 30 de octubre de 2025; el dominio antiguo redirige y el cliente de Python oficial es ahora massive.
  • Los límites vienen en unidades distintas (por minuto, por hora, por día): hay que pasarlos a la misma antes de comparar, y estimar con ellos cuánto tarda la descarga.
  • Un cliente serio sigue la paginación (next_url, next_page_token), respeta los 429 y Retry-After, y lee la clave de una variable de entorno.
  • Precios y planes cambian: cítalos con la fecha en que los leíste y vuelve a leerlos antes de pagar.

Recursos

Datos