---
title: "1.3.3 APIs de datos retail y prosumer"
description: "Massive (antes Polygon.io), Alpaca, Tiingo, EODHD, Alpha Vantage, Twelve Data y FMP: planes, límites y un cliente REST con paginación y errores 429."
url: "https://pythonparatrading.com/curso-python-trading/nivel-1/apis-de-datos-retail-y-prosumer/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-1/apis-de-datos-retail-y-prosumer.png"
---

# 1.3.3 · 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.

- Curso: [Nivel 1: Datos financieros, mercados y análisis](https://pythonparatrading.com/curso-python-trading/nivel-1/)
- Módulo: 1.3 · Fuentes de datos de mercado
- Duración: 55 min
- Requisitos: [1.3.1 · Tipos de datos de mercado: EOD, barras, ticks, libro y referencia](https://pythonparatrading.com/curso-python-trading/nivel-1/tipos-de-datos-de-mercado/), [1.3.2 · Fuentes de datos gratuitas](https://pythonparatrading.com/curso-python-trading/nivel-1/fuentes-de-datos-gratuitas/)

## Al terminar esta lección

- Situar a Massive (antes Polygon.io), Alpaca, Tiingo, EODHD, Alpha Vantage, Twelve Data y Financial Modeling Prep por lo que da cada plan: historia, retraso, límites y precio.
- Convertir los límites de los planes gratuitos a una misma unidad y estimar cuánto tarda una descarga histórica.
- Leer la respuesta JSON de un endpoint de barras (marcas en milisegundos, `results`, `next_url`) y pasarla a un DataFrame.
- Escribir un cliente REST que siga `next_url`, reaccione a un 429 con `Retry-After` y no deje la clave en el código.

## 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 nuevas[^1]. 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:

| Plan | Precio al mes | Historia | Retraso | Peticiones |
|---|---|---|---|---|
| Stocks Basic | 0 USD | 2 años | Fin de día | 5 por minuto |
| Stocks Starter | 29 USD | 5 años | 15 minutos | Ilimitadas |
| Stocks Developer | 79 USD | 10 años | 15 minutos | Ilimitadas; añade operaciones |
| Stocks Advanced | 199 USD | 20+ años | Tiempo real | Ilimitadas; 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

| Proveedor | Plan gratuito | Plan de pago más barato con acciones de EE. UU. | Detalle |
|---|---|---|---|
| Alpaca | Basic: tiempo real solo de IEX, histórico desde 2016 sin los últimos 15 minutos, 200 llamadas por minuto | Algo Trader Plus: 99 USD al mes, todas las bolsas de EE. UU., 10.000 llamadas por minuto | El bróker da los datos con la cuenta; paginación con `next_page_token` |
| Tiingo | Starter: 50 peticiones por hora, 1.000 al día, 500 símbolos al mes | Power: 30 USD al mes (300 al año) para particulares; 50 USD para uso comercial interno | Fin de día con más de 30 años, con precios ajustados, dividendos y splits en la misma respuesta |
| EODHD | 20 llamadas al día, 1 año de historia | EOD Historical All-World: 19,99 USD al mes | Cobra cada llamada en unidades: 1 por fin de día, 5 por intradía, 10 por fundamentales |
| Alpha Vantage | 25 peticiones al día | 49,99 USD al mes: 75 peticiones por minuto y datos de EE. UU. con 15 minutos de retraso | El tiempo real y el diferido son de pago |
| Twelve Data | Basic: 8 créditos por minuto, 800 al día | Grow: desde 29 USD al mes (55 créditos por minuto) | Facturado al año, más barato |
| Financial Modeling Prep | Basic: 250 llamadas al día, 5 años de fin de día | Starter: 29 USD al mes (19 USD al mes con facturación anual), 300 por minuto | Fuerte en fundamentales |

Fuentes: la página de precios o de planes de cada proveedor, leída el 21/10/2026[^2]. 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.* Barras horizontales con el precio mensual: EODHD fin de día 19,99 dólares; Massive Starter 29, destacado; Twelve Data Grow 29; FMP Starter 29; Tiingo Power 30; Alpha Vantage 49,99; Alpaca Algo Trader Plus 99.

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.

```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)
```
```output
                   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:

```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")
```
```output
                   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.* Gráfico de puntos en escala logarítmica con las peticiones al día de cada plan gratuito: Alpaca Basic 288.000 (200 por minuto), Massive Basic 7.200 (5 por minuto), Tiingo 1.000, Twelve Data 800, FMP 250, Alpha Vantage 25, destacado, y EODHD 20. Del primero al último hay cuatro órdenes de magnitud.

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 defecto[^3].

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

```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")
```
```output
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.

```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.

```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")
```
```output
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.

```python raises
from urllib.error import HTTPError
from urllib.request import urlopen

urlopen(f"{URL}?limit=50", timeout=5)
```
```output
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):

```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")
```
```output
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.

```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()
```
```output
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):

```python skip title="massive_barras.py"
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:

```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")
```
```output
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.**

```python
limites["dias_50"] = (peticiones / (0.5 * limites["al_dia"])
                      ).apply(math.ceil)
limites.loc[limites["dias_50"] == 1].index.tolist()
```
```output
['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.

2. 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.**

```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))
```
```output
(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.

3. 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).

[^1]: Massive, [«Polygon.io is Now Massive»](https://massive.com/blog/polygon-is-now-massive), 30/10/2025, y [página de precios](https://massive.com/pricing), consultadas el 21/10/2026.
[^2]: Páginas de precios consultadas el 21/10/2026: [Alpaca](https://docs.alpaca.markets/us/docs/about-market-data-api), [Tiingo](https://www.tiingo.com/about/pricing), [EODHD](https://eodhd.com/pricing), [Alpha Vantage](https://www.alphavantage.co/premium/), [Twelve Data](https://twelvedata.com/pricing), [Financial Modeling Prep](https://site.financialmodelingprep.com/developer/docs/pricing).
[^3]: Massive, [Custom Bars (OHLC)](https://massive.com/docs/rest/stocks/aggregates/custom-bars) y [REST API Quickstart](https://massive.com/docs/rest/quickstart), consultadas el 21/10/2026.

## 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?

   - A. Basic (gratuito)
   - B. Starter (29 USD al mes)
   - C. Developer (79 USD al mes)
   - D. Advanced (199 USD al mes)

   *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?

   - A. Ignorarlo si ya tiene datos
   - B. Pedir esa URL (añadiendo la clave) hasta que la respuesta no traiga `next_url`
   - C. Aumentar `limit` hasta que desaparezca
   - D. Pedir la misma URL otra vez

   *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?

   - A. Reintentar inmediatamente en bucle
   - B. Esperar al menos un segundo y repetir la misma petición
   - C. Pasar a la página siguiente
   - D. Abortar la descarga

   *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?

   - A. Uno
   - B. Cinco
   - C. Veinte
   - D. Cien

   *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?

   - A. Porque ocupa mucho
   - 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
   - C. Porque las APIs no aceptan claves en el código
   - D. Porque cambia cada día

   *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

### Research

- [OpenBB Platform: datos de mercado gratuitos](https://pythonparatrading.com/openbb-datos-de-mercado-gratuitos/) · Research-0014 · 10 min

### Documentación

- [Massive: precios (planes de acciones)](https://massive.com/pricing) · massive.com
- [Massive: «Polygon.io is Now Massive» (30 de octubre de 2025)](https://massive.com/blog/polygon-is-now-massive) · massive.com
- [Massive: Custom Bars (OHLC), endpoint de agregados](https://massive.com/docs/rest/stocks/aggregates/custom-bars) · massive.com
- [Alpaca: About Market Data API (planes Basic y Algo Trader Plus)](https://docs.alpaca.markets/us/docs/about-market-data-api) · docs.alpaca.markets

### Datos

- [Datos del nivel 1: NOVA diario sin ajustar (CSV)](https://pythonparatrading.com/curso/datos/nivel-1/nova_diario_sin_ajustar.csv) · Descarga · nova_diario_sin_ajustar.csv

Lección anterior: [1.3.2 · Fuentes de datos gratuitas](https://pythonparatrading.com/curso-python-trading/nivel-1/fuentes-de-datos-gratuitas/)

Lección siguiente: [1.3.4 · Proveedores de datos profesionales](https://pythonparatrading.com/curso-python-trading/nivel-1/proveedores-de-datos-profesionales/)
