Lección 6.7.4 · 60 min
Proveedores y due diligence de datos alternativos
Un proveedor de datos vende una muestra, una ficha y una historia de varios años. La muestra se prueba en un día; la historia decide si el backtest dice la verdad. Esta lección recorre lo que dicen hacer Neudata, Nasdaq Data Link y Eagle Alpha y ejecuta una due diligence completa sobre los datos de tarjetas del curso: la historia reconstruida infla el R² de 0,28 a 0,39, el panel cambia de composición y la legalidad se comprueba con el texto de MAR y del RGPD.
- Módulo
- 6.7 · Datos alternativos
- Duración
- 60 min
Lecciones del nivel 6
6.1 · NLP financiero clásico y moderno
6.2 · LLMs para investigación y trading
6.3 · RAG financiero
6.4 · Agentes de IA para trading
- 6.4.1 Agentes con herramientas: tool calling, ReAct y planificación
- 6.4.2 Marcos de agentes y MCP
- 6.4.3 Arquitecturas multiagente tipo TradingAgents
- 6.4.4 Evidencia y límites de los agentes
- 6.4.5 Guardarraíles para agentes: el agente propone, el motor decide
- 6.4.6 Agentes de investigación: hipótesis, alpha mining y revisión
6.5 · IA como copiloto del quant
6.6 · Big data para trading
- 6.6.1 Cuándo los datos son «big»: ticks, libros, opciones y universos
- 6.6.2 Parquet, Arrow, Polars en streaming y DuckDB fuera de memoria
- 6.6.3 Dask, Spark y Ray: de una máquina a un clúster
- 6.6.4 Bases de series temporales: TimescaleDB, QuestDB, ClickHouse, ArcticDB y kdb+
- 6.6.5 Almacenamiento en la nube, lakehouse (Delta, Iceberg) y GPU (RAPIDS, cuDF)
6.7 · Datos alternativos
Quién vende y quién responde
La lección 6.7.1 midió el mercado y la 6.7.2 recorrió los tipos de dato. Esta empieza donde empieza una compra real: un gestor ha visto una ficha en un catálogo, el proveedor le ofrece una muestra gratuita durante unas semanas y la pregunta es si pagar. Entre el gestor y el proveedor suele haber intermediarios. Lo que dicen hacer los tres que cita el temario, leído en sus propias webs el 2026-10-23:
| Empresa | Qué dice hacer (su web, 2026-10-23) | Qué no hace por el comprador |
|---|---|---|
| Neudata | Catálogo de «7,000+ datasets» de «4,200+» proveedores, informes de datasets escritos por analistas, información de precios de «over 2,000 data products», búsqueda de datos por encargo y avisos de datos de baja calidad | Probar el dataset con la estrategia del comprador |
| Eagle Alpha | Plataforma y asesoría: «over 2,500 alternative data products» en su portada y «over 2,600 datasets» con una taxonomía de 16 categorías y 56 subcategorías en sus preguntas frecuentes; Surveyor, una herramienta para gestionar los cuestionarios de due diligence (DDQ) de los proveedores | Decidir si el uso es legal para el comprador |
| Nasdaq Data Link | Plataforma en la nube con «more than 350 trusted data sets» por API, con clientes de R, Python y Excel; el cliente de Python tiene get_table, export_table y get_point_in_time para pedir una tabla «as of» una fecha | Garantizar que un dataset de terceros no tenga sesgos |
Las cifras son de cada empresa sobre sí misma, sin auditar, y cambian entre páginas de la misma web (Eagle Alpha da 2.500 en una y 2.600 en otra). Sirven para saber qué ofrece cada uno, no para compararlos. Ninguno de los tres puede responder la pregunta que importa: si este dataset mejora esta estrategia con los datos tal como habrían llegado. Eso es la due diligence del comprador, y tiene un orden:
Fuente Mermaid
flowchart TB A[Ficha y catálogo] --> B[NDA y muestra] B --> C[Pruebas técnicas] C --> D[Pruebas de señal] D --> E[Revisión legal] E --> F[Contrato] C --> X[Descartar] D --> X E --> X
Las pruebas técnicas comprueban que el dato es lo que dice ser (cobertura, historia, entregas, sesgos); las de señal, que aporta algo (lección 6.7.5); la revisión legal, que se puede usar. Cualquiera de las tres puede terminar la compra. El orden importa: un dataset con historia reconstruida da pruebas de señal brillantes y engañosas, así que la historia se examina antes.
La ficha del dataset
Lo primero es reconstruir la ficha a partir de los ficheros, no copiarla del folleto: cuántos comercios, qué fechas,
cuándo llega cada dato. El proveedor entrega dos cosas. alt6_tarjetas_pit.parquet es el dato tal como se
entregó cada día (point-in-time, PIT): el gasto del día t llega el día t + 3 y es preliminar. Y
alt6_tarjetas_historico.parquet es la historia que el proveedor entrega al firmar el contrato, el 2023-07-03.
import numpy as np
import pandas as pd
pit = pd.read_parquet("alt6_tarjetas_pit.parquet")
hist = pd.read_parquet("alt6_tarjetas_historico.parquet")
mapa = pd.read_csv("alt6_comercios_verdad.csv")
for nombre, df in [("PIT", pit), ("histórico", hist)]:
print(f"{nombre:10s} {len(df):7,d} filas · "
f"{df['descriptor'].nunique()} comercios · "
f"{df['fecha'].min():%Y-%m-%d} a "
f"{df['fecha'].max():%Y-%m-%d}")
retraso = (pit["fecha_entrega"] - pit["fecha"]).dt.days
print("días entre la fecha y la entrega:", retraso.unique())
print(mapa["relacion"].value_counts().to_string())PIT 359,410 filas · 127 comercios · 2018-01-01 a 2025-09-30
histórico 254,889 filas · 127 comercios · 2018-01-01 a 2023-06-30
días entre la fecha y la entrega: [3]
relacion
matriz 69
marca 22
filial 18
sin_relacion 18127 comercios: 109 son de alguna de las 25 empresas (la matriz, una marca o una filial) y 18 no tienen relación
aunque se llamen parecido. Aquí se usa el mapeo correcto (alt6_comercios_verdad.csv) como si lo diera el proveedor;
construirlo desde cero es la primera parte de la lección 6.7.5. La historia acaba el 2023-06-30, tres días antes de
la entrega.
La pregunta práctica de la ficha es si el dato llega a tiempo. Un dataset de ventas sirve si el trimestre está completo antes de que la empresa publique sus resultados:
emp = pd.read_csv("acciones6_empresas.csv")
ventas = pd.read_csv("acciones6_ventas.csv",
parse_dates=["fin_trimestre", "fecha_publicacion"])
ventas = ventas.merge(emp[["ticker", "id_empresa"]], on="ticker")
cubiertas = mapa["id_empresa"].dropna().unique()
v = ventas[ventas["id_empresa"].isin(cubiertas)]
# el último día del trimestre se entrega 3 días después
completo = v["fin_trimestre"] + pd.Timedelta(days=3)
margen = (v["fecha_publicacion"] - completo).dt.days
print(f"{len(v)} resultados · margen (días): mínimo {margen.min()}, "
f"mediana {margen.median():.0f}, máximo {margen.max()}")800 resultados · margen (días): mínimo 17, mediana 36, máximo 62El trimestre completo llega entre 17 y 62 días antes de los resultados: hay tiempo para calcular y operar. Con un proveedor real, esta cifra se comprueba con las fechas de entrega de la muestra, no con la promesa comercial: los retrasos que se publicitan suelen ser los del mejor día.
Point-in-time frente a historia reconstruida
Un proveedor que lleva años vendiendo mejora su producto: incorpora bancos, corrige el mapeo de comercios, ajusta los pesos. Cuando un cliente nuevo pide historia, lo habitual es entregarle la serie reconstruida con la metodología actual (backfill), no la que vieron los clientes de entonces. Con datos de precios eso es una molestia; con datos que se calibran con las cifras que pretenden anticipar, es anticipación pura.
La historia del curso hace las tres cosas típicas: completa los días con las operaciones que llegaron tarde, repondera el panel antiguo al tamaño y la composición actuales y calibra cada trimestre con las ventas ya publicadas. La forma de verlo es poner las dos versiones una al lado de la otra. Para que la comparación sea limpia hace falta una función que devuelva lo que se sabía en una fecha:
def a_fecha(df, fecha):
"""Filas entregadas hasta `fecha` (incluida)."""
return df[df["fecha_entrega"] <= pd.Timestamp(fecha)]
def por_trimestre(df):
d = df.merge(mapa.dropna(subset=["id_empresa"]),
on="descriptor")
d["fin_trimestre"] = (d["fecha"].dt.to_period("Q")
.dt.end_time.dt.normalize())
return d.groupby(["id_empresa", "fin_trimestre"])["gasto_usd"].sum()
ejemplo = a_fecha(pit, "2021-04-02")
print("último día conocido el 2021-04-02:",
ejemplo["fecha"].max().date())
gp, gh = por_trimestre(pit), por_trimestre(hist)
r = (gh / gp).dropna().rename("ratio").reset_index()
r["tramo"] = np.select(
[r["fin_trimestre"] < "2021-03-31",
r["fin_trimestre"] == "2021-03-31",
r["fin_trimestre"] <= "2023-03-31"],
["1 antes del banco nuevo", "2 trimestre mixto",
"3 resultados publicados"], "4 sin publicar (2023T2)")
r.groupby("tramo")["ratio"].agg(["count", "mean", "std"]).round(3)último día conocido el 2021-04-02: 2021-03-30
count mean std
tramo
1 antes del banco nuevo 300 1.431 0.029
2 trimestre mixto 25 1.277 0.046
3 resultados publicados 200 1.053 0.066
4 sin publicar (2023T2) 25 1.067 0.001El cociente entre la historia y el dato entregado cuenta lo que hizo el proveedor:
- Antes del banco nuevo la historia es un 43 % mayor: la reponderación al panel actual (que tiene un 35 % más de tarjetas) y la parte de las operaciones tardías.
- 2023T2 es el único trimestre de la historia cuyas ventas no se conocían el día de la entrega. Su cociente es 1,067 en las 25 empresas, con una desviación de 0,001: solo se le añadieron las operaciones que llegan tarde (un 6 % del gasto preliminar, 1 / 0,94 ≈ 1,064).
- Con los resultados publicados, el cociente varía de una empresa y un trimestre a otro (desviación 0,066): la historia se ha movido hacia unas ventas que en esa fecha nadie conocía.
Esta prueba no necesita conocer la verdad del generador: basta comparar la historia con el dato en vivo de los trimestres que se solapan, o con la entrega de un trimestre que todavía no tenía resultados. Si el proveedor no puede dar el archivo de lo que entregó cada día, la comparación se hace con los primeros meses de prueba y con preguntas directas: cuándo cambió la metodología y si la historia se recalcula con ella.
Algunas plataformas ofrecen consultas «as of» de forma explícita. El cliente de Python de Nasdaq Data Link (versión
1.0.4, licencia MIT) tiene get_point_in_time con tres modos (asofdate, from, between), además de get_table
para tablas paginadas; según su documentación, sin clave de API algunas tablas devuelven solo una muestra. El código,
sin ejecutar (necesita red y clave; la tabla es la del ejemplo de la documentación):
import nasdaqdatalink
# la clave, en la variable de entorno NASDAQ_DATA_LINK_API_KEY
tabla = nasdaqdatalink.get_table(
"ZACKS/FC", paginate=True, ticker=["AAPL", "MSFT"],
per_end_date={"gte": "2015-01-01"},
qopts={"columns": ["ticker", "per_end_date"]})
# la tabla tal como estaba en una fecha
vista = nasdaqdatalink.get_point_in_time(
"DATABASE/CODE", interval="asofdate", date="2020-01-01")La consecuencia en la señal es lo que importa. La señal de la lección 6.7.5 compara el crecimiento interanual del gasto por tarjeta con el que implica el consenso; aquí basta con su R² frente a la sorpresa de ventas, medido con cada versión del dato:
panel = pd.read_csv("alt6_tarjetas_panel.csv", parse_dates=["fecha"])
panel["fin_trimestre"] = (panel["fecha"].dt.to_period("Q")
.dt.end_time.dt.normalize())
tarjetas = panel.groupby("fin_trimestre")["tarjetas_activas"].mean()
T = v.set_index(["id_empresa", "fin_trimestre"]).sort_index()
def interanual(s):
return np.log(s).groupby(level=0).diff(4)
T["g_pit"] = interanual(
gp / tarjetas.reindex(gp.index.get_level_values(1)).to_numpy())
T["g_hist"] = interanual(gh) # ya va al panel actual
T["g_cons"] = (np.log(T["consenso_ventas"])
- np.log(T["ventas"].groupby(level=0).shift(4)))
T["sorpresa"] = np.log(T["ventas"] / T["consenso_ventas"])
f = T.index.get_level_values(1)
def r2(col, desde, hasta):
d = T[(f >= desde) & (f <= hasta)].dropna(subset=[col])
x = d[col] - d["g_cons"]
return len(d), x.corr(d["sorpresa"]) ** 2
for nombre, col, a, b in [
("histórico, 2019T1-2023T1", "g_hist", "2019", "2023-03-31"),
("PIT, 2019T1-2023T1", "g_pit", "2019", "2023-03-31"),
("PIT tras la entrega, 2023T3-", "g_pit", "2023-09-30",
"2025-12-31")]:
n, valor = r2(col, a, b)
print(f"{nombre:30s} n = {n} R² = {valor:.3f}")
verdad = pd.read_csv("alt6_verdad_trimestral.csv",
parse_dates=["fin_trimestre"])
vv = verdad[verdad["ticker"].isin(v["ticker"])
& (verdad["fin_trimestre"] >= "2019")]
print(f"techo (parte medible verdadera) R² = "
f"{vv['pred'].corr(vv['sorpresa_ventas']) ** 2:.3f}")histórico, 2019T1-2023T1 n = 425 R² = 0.391
PIT, 2019T1-2023T1 n = 425 R² = 0.276
PIT tras la entrega, 2023T3- n = 225 R² = 0.277
techo (parte medible verdadera) R² = 0.567Es la figura de la cabecera. Con la historia entregada, la señal explica el 39,1 % de la varianza de las sorpresas entre 2019 y 2023; con el dato tal como llegó cada día, el 27,6 % en los mismos 425 empresa-trimestres. El gestor que evaluase el dataset con la historia esperaría un 39 %, y después de contratar vería un 27,7 %: exactamente lo que el dato en vivo daba antes. La diferencia era la calibración con las ventas. El techo (0,567) es la parte de la sorpresa que miden los datos de verdad, el máximo que podría alcanzar un método perfecto.
El patrón es general y por eso es una prueba de due diligence: una caída del rendimiento justo en la fecha de entrega de la historia delata un backfill, igual que la caída del acierto de un LLM en su fecha de corte delata su memoria (lección 6.2.5).
Sesgos del panel
Un panel de tarjetas es una muestra de conveniencia: los titulares de los bancos o apps que ceden datos al proveedor. La lección 6.7.2 mostró el salto del panel el 2021-03-01 en el gasto diario. En la due diligence se mira su efecto en la magnitud que se va a usar, el crecimiento interanual de cada trimestre:
T["bruto"] = gp
T["por_tarjeta"] = gp / tarjetas.reindex(
gp.index.get_level_values(1)).to_numpy()
g = pd.DataFrame({c: interanual(T[c])
for c in ["ventas", "bruto", "por_tarjeta"]}).dropna()
mediana = 100 * g.groupby(level=1).median()
mediana.loc["2020-12-31":"2022-06-30"].round(1) ventas bruto por_tarjeta
fin_trimestre
2020-12-31 4.6 13.5 3.6
2021-03-31 5.4 22.5 1.0
2021-06-30 7.5 47.6 7.5
2021-09-30 6.7 48.5 8.5
2021-12-31 7.7 46.3 6.3
2022-03-31 5.5 34.0 5.3
2022-06-30 4.6 15.4 5.4Durante cuatro trimestres el gasto bruto crece un 46-48 % interanual mientras las ventas crecen un 7-8 %: un modelo entrenado con el gasto bruto vería un auge de consumo. Fuera del salto, el bruto también crece unos 10 puntos más que las ventas cada año, porque el panel gana un 10 % de tarjetas anual. Normalizado por tarjetas activas, sigue a las ventas.
El tamaño se corrige con una división. La composición es más difícil. Con el banco nuevo, y año tras año, el panel
tiene más titulares jóvenes (del 30 % en 2018 al 47 % en 2025), y las marcas que prefieren ganan peso en el panel sin
vender más. La prueba es medir, empresa a empresa, cuánto se desvía el gasto por tarjeta de sus ventas a lo largo de
la muestra. El proveedor no publica la clientela de cada marca, pero el generador sí (alt6_tarjetas_verdad.csv), y
permite ver si la desviación tiene un patrón:
clientela = pd.read_csv("alt6_tarjetas_verdad.csv",
index_col="id_empresa")["clientela_joven"]
deriva = 100 * (g["por_tarjeta"] - g["ventas"]).groupby(level=0).mean()
deriva = pd.DataFrame({"deriva_pp": deriva,
"clientela_joven": clientela}).dropna()
print(deriva.sort_values("clientela_joven").iloc[[0, 1, -2, -1]]
.round(2).to_string())
print("correlación:", round(deriva.corr().iloc[0, 1], 3))
b = np.polyfit(deriva["clientela_joven"], deriva["deriva_pp"], 1)[0]
print(f"pendiente: {b:.2f} pp de crecimiento anual por unidad") deriva_pp clientela_joven
id_empresa
E011 -1.80 0.12
E117 -1.62 0.12
E120 2.04 0.95
E105 2.39 0.99
correlación: 0.982
pendiente: 4.70 pp de crecimiento anual por unidadLa deriva tiene una correlación de 0,98 con la clientela joven: el gasto por tarjeta de una empresa con clientela casi toda joven crece unos 2 puntos al año más que sus ventas, y el de una con clientela mayor, casi 2 puntos menos. En una señal de sorpresa de ventas, ese sesgo entra como un error sistemático por empresa. Con datos reales la clientela no se conoce, pero la deriva sí se mide (gasto frente a ventas publicadas, empresa a empresa) y se pregunta al proveedor cómo cambia la composición del panel y si la repondera (por edad, renta, región). La reponderación es legítima si se aplica igual en vivo y en la historia; lo que no lo es es aplicarla solo hacia atrás.
Supervivencia y entregas
El sesgo de supervivencia de los universos de acciones (lección 1.4.4) tiene su versión en los datasets: un proveedor que solo entrega historia de los comercios que siguen activos hoy borra las tiendas que cerraron, y el crecimiento pasado parece mejor. La prueba es contar los comercios con datos en cada periodo, en las dos versiones:
def vivos(df):
m = df.assign(mes=df["fecha"].dt.to_period("M"))
return m.groupby("mes")["descriptor"].nunique()
print("PIT: comercios por mes, mínimo y máximo:",
vivos(pit).min(), vivos(pit).max())
print("histórico:", vivos(hist).min(), vivos(hist).max())
primero = pit.groupby("descriptor")["fecha"].agg(["min", "max"])
print("altas después de 2018-01-01:",
int((primero["min"] > "2018-01-01").sum()),
"· bajas antes de 2025-09-30:",
int((primero["max"] < "2025-09-30").sum()))
print("comercios del PIT que faltan en la historia:",
len(set(pit["descriptor"]) - set(hist["descriptor"])))PIT: comercios por mes, mínimo y máximo: 127 127
histórico: 127 127
altas después de 2018-01-01: 0 · bajas antes de 2025-09-30: 0
comercios del PIT que faltan en la historia: 0En estos datos no desaparece ningún comercio: los 127 están todos los meses en las dos versiones. La prueba pasa, y eso también es información: el panel sintético no tiene altas ni bajas de comercios, así que el sesgo de supervivencia no se puede medir aquí. Con un proveedor real, lo normal es lo contrario: tiendas que cierran, comercios que cambian de pasarela de pago (y de descriptor) y marcas que se venden. Las preguntas son si la historia incluye los comercios dados de baja y si el mapeo a empresas es el de cada fecha o el de hoy (una marca vendida en 2020 no debe sumar a su comprador en 2018).
El retraso se mide igual. Ya se vio que cada día llega tres días después; el dato llega además incompleto: el cociente de 1,067 del trimestre 2023T2 dice que al gasto preliminar le falta un 6 % que llega más tarde. Si el proveedor revisa sus entregas, hay que guardar cada versión con su fecha (como los fundamentales point-in-time del nivel 5), y no sobrescribir la anterior.
Legalidad: información privilegiada y datos personales
La revisión legal no la hace el analista cuantitativo, pero él tiene que saber qué preguntar y qué hacen los datos. Dos normas de la UE cubren lo esencial. Lo que sigue resume su texto, leído en la versión publicada en el BOE del Diario Oficial de la UE el 2026-10-231Nota 1 · El texto se leyó en las versiones del DOUE que publica el BOE: MAR y RGPD, consultadas el 2026-10-23. EUR-Lex publica el mismo texto (enlaces en los recursos) y las versiones consolidadas con las modificaciones posteriores.; no es asesoramiento jurídico.
Información privilegiada (MAR, Reglamento (UE) 596/2014). El artículo 7.1.a) la define como «la información de carácter concreto que no se haya hecho pública, que se refiera directa o indirectamente a uno o varios emisores o a uno o varios instrumentos financieros» y que, de hacerse pública, podría influir de manera apreciable en sus precios. El artículo 8 define las operaciones con ella y el 14 las prohíbe, junto con recomendarlas y comunicarla ilícitamente. Para los datos alternativos, el texto clave es el considerando 28: «Los análisis y los cálculos elaborados a partir de datos públicos no deben considerarse en sí mismos como información privilegiada», con excepciones (por ejemplo, si el mercado espera sistemáticamente su publicación y contribuye a la formación de precios), y pide a los agentes valorar «en qué medida la información no es pública». El riesgo no está en agregar datos de miles de tarjetas, sino en su origen: un dataset que contenga cifras internas del emisor, cedidas por un empleado o por un proveedor obligado a confidencialidad, puede ser información privilegiada aunque llegue en un fichero comercial.
Datos personales (RGPD, Reglamento (UE) 2016/679). El artículo 4.1 define datos personales como «toda información sobre una persona física identificada o identificable»; el 4.5, la seudonimización (datos que no pueden atribuirse a una persona «sin utilizar información adicional», guardada por separado). El considerando 26 aclara que los datos seudonimizados siguen siendo datos personales y que el reglamento no se aplica a la información anónima, la que no permite identificar a nadie con los medios que razonablemente puedan usarse. Un panel de transacciones «anonimizado» con un identificador aleatorio por titular es, en general, seudonimizado.
La diferencia no es teórica. de Montjoye y coautores (2015) estudiaron tres meses de transacciones de 1,1 millones de personas y encontraron que cuatro puntos espaciotemporales bastan para reidentificar al 90 %. La celda siguiente lo reproduce en pequeño con titulares simulados: 20.000 personas que compran sobre todo en sus seis comercios habituales, de 400, durante 90 días.
rng = np.random.default_rng(0)
n_pers, n_com, dias = 20_000, 400, 90
habituales = rng.integers(0, n_com, size=(n_pers, 6))
quien_compra = np.repeat(np.arange(n_pers),
rng.poisson(25, n_pers) + 1)
comercio = habituales[quien_compra,
rng.integers(0, 6, len(quien_compra))]
otro = rng.random(len(comercio)) < 0.2 # compras fuera de lo habitual
comercio[otro] = rng.integers(0, n_com, otro.sum())
dia = rng.integers(0, dias, len(comercio))
tx = pd.DataFrame({"persona": quien_compra, "comercio": comercio,
"dia": dia}).drop_duplicates()
tx["punto"] = tx["comercio"] * dias + tx["dia"]
personas_en = tx.groupby("punto")["persona"].apply(set).to_dict()
puntos_de = tx.groupby("persona")["punto"].apply(np.array)
muestra = rng.choice(n_pers, 2000, replace=False)
for k in (1, 2, 3, 4):
unicos = 0
for p in muestra:
elegidos = rng.choice(puntos_de[p], k, replace=False)
candidatos = set.intersection(
*(personas_en[x] for x in elegidos))
unicos += len(candidatos) == 1
print(f"{k} compra(s) conocida(s): {unicos / 20:.1f} % únicos")
celdas = tx.groupby(["comercio", "dia"])["persona"].nunique()
print(f"celdas comercio-día con menos de 10 personas: "
f"{(celdas < 10).mean():.1%}")1 compra(s) conocida(s): 0.0 % únicos
2 compra(s) conocida(s): 96.0 % únicos
3 compra(s) conocida(s): 100.0 % únicos
4 compra(s) conocida(s): 100.0 % únicos
celdas comercio-día con menos de 10 personas: 9.9%Con una compra conocida (el comercio y el día, por ejemplo por un ticket o una foto en redes) nadie queda identificado: cada comercio recibe de media unas 14 personas al día. Con dos, el 96 % de los titulares de la muestra queda singularizado; con tres, todos. Un identificador aleatorio no protege. Lo que reduce el riesgo es agregar: el proveedor del curso entrega gasto y número de operaciones por comercio y día, no compras de personas. Pero la agregación tiene su propio límite: en la simulación, el 9,9 % de las celdas comercio-día tiene menos de 10 personas, y una celda con dos o tres compradores casi vuelve a ser un dato individual. Por eso los proveedores serios suprimen o agrupan las celdas pequeñas, y el umbral que usan es una pregunta de la due diligence. Para el comprador, la diferencia entre recibir transacciones con identificador de titular y recibir agregados con umbral es la diferencia entre tratar datos personales y, en general, no tratarlos.
Las preguntas de la revisión legal salen de esas dos normas:
Fuente Mermaid
flowchart TB
A[Origen del dato] --> B{Datos del emisor no públicos}
B -->|sí| X[Riesgo MAR: parar]
B -->|no| C{Datos de personas}
C -->|no| OK[Revisión de contrato]
C -->|sí| D{Agregado y anónimo}
D -->|sí| OK
D -->|no| E[Base jurídica RGPD]
E --> F[Área legal decide]En la práctica eso son preguntas por escrito al proveedor (el DDQ): de dónde salen los datos y con qué contrato, si los titulares consintieron y para qué, si hay datos de empleados o de proveedores del emisor, cómo se agregan, qué umbral mínimo de personas por celda se usa y qué pasa si un titular ejerce su derecho de supresión. Las respuestas se archivan con la versión del dataset: si cambian, la revisión se repite.
El informe de due diligence
Todo lo anterior cabe en una tabla que se pueda repetir cada vez que el proveedor entrega una versión nueva. Las pruebas técnicas salen del código; las legales, de las respuestas del proveedor:
n_in, r2_hist = r2("g_hist", "2019", "2023-03-31")
_, r2_pit = r2("g_pit", "2019", "2023-03-31")
informe = pd.DataFrame([
("historia PIT disponible", "sí (archivo diario)", "ok"),
("inflación del R² con la historia",
f"{r2_hist:.3f} frente a {r2_pit:.3f}", "descartar historia"),
("cambio de tamaño del panel", "+35 % el 2021-03-01",
"normalizar"),
("deriva por composición", "corr. 0,98 con clientela",
"corregir o modelar"),
("comercios que desaparecen", "0 de 127", "ok"),
("retraso de entrega", "3 días; 6 % llega tarde", "ok"),
("margen hasta resultados", f"{margen.min()} días o más", "ok"),
("origen y consentimiento", "pregunta al proveedor",
"pendiente"),
("agregación mínima por celda", "pregunta al proveedor",
"pendiente"),
], columns=["prueba", "resultado", "decisión"])
print(informe.to_string(index=False)) prueba resultado decisión
historia PIT disponible sí (archivo diario) ok
inflación del R² con la historia 0.391 frente a 0.276 descartar historia
cambio de tamaño del panel +35 % el 2021-03-01 normalizar
deriva por composición corr. 0,98 con clientela corregir o modelar
comercios que desaparecen 0 de 127 ok
retraso de entrega 3 días; 6 % llega tarde ok
margen hasta resultados 17 días o más ok
origen y consentimiento pregunta al proveedor pendiente
agregación mínima por celda pregunta al proveedor pendienteLa decisión de este caso: el dataset es usable con el archivo point-in-time, normalizado por tarjetas, con la deriva de composición tratada y descartando la historia calibrada para cualquier backtest. Con eso, la lección 6.7.5 construye la señal y mide cuánto alfa queda.
Ejercicios
- Repite la prueba de la inflación con un corte distinto: mide el R² del histórico y del PIT solo en 2019-2020 y solo en 2021T2-2023T1. ¿En qué tramo infla más la historia?
Solución
for a, b in [("2019", "2020-12-31"), ("2021-06-30", "2023-03-31")]:
_, rh = r2("g_hist", a, b)
n, rp = r2("g_pit", a, b)
print(f"{a} a {b}: n = {n} histórico {rh:.3f} PIT {rp:.3f}")2019 a 2020-12-31: n = 200 histórico 0.437 PIT 0.330
2021-06-30 a 2023-03-31: n = 200 histórico 0.345 PIT 0.232La historia infla los dos tramos casi lo mismo, unos 11 puntos de R² (0,437 frente a 0,330 en 2019-2020; 0,345 frente a 0,232 en 2021T2-2023T1). El nivel de cada tramo cambia con la muestra (200 empresa-trimestres dan un R² ruidoso), pero la diferencia entre versiones es estable: es la huella de la calibración, que se aplicó a todos los trimestres con resultados publicados.
- Cambia la simulación de reidentificación para que el proveedor entregue la semana en lugar del día de cada compra. ¿Cuántas compras hacen falta ahora para singularizar a la mayoría?
Solución
tx["punto_sem"] = tx["comercio"] * 13 + tx["dia"] // 7
personas_sem = tx.groupby("punto_sem")["persona"].apply(set).to_dict()
puntos_sem = tx.groupby("persona")["punto_sem"].apply(
lambda s: np.unique(s.to_numpy()))
for k in (2, 3, 4):
unicos = 0
for p in muestra[:500]:
pts = puntos_sem[p]
elegidos = rng.choice(pts, min(k, len(pts)), replace=False)
cand = set.intersection(*(personas_sem[x] for x in elegidos))
unicos += len(cand) == 1
print(f"{k} compras: {unicos / 5:.1f} % únicos")2 compras: 65.4 % únicos
3 compras: 98.0 % únicos
4 compras: 99.6 % únicosCon la semana, dos compras singularizan al 65 % (frente al 96 % con el día), tres al 98 % y cuatro a casi todos. Reducir la resolución sube el umbral sin eliminar el riesgo, que es lo que encontraron de Montjoye y coautores con datos reales: incluso con información gruesa en todas las dimensiones el anonimato es escaso.
- Un proveedor te ofrece un dataset de «intenciones de compra» obtenido de encuestas a directivos de compras de grandes cadenas, entregado dos semanas antes de que las cadenas publiquen sus ventas. ¿Qué preguntas harías antes de probar la señal?
Solución
Primero las de MAR: si los encuestados trabajan en el emisor o en sus proveedores y comparten cifras internas no publicadas (ventas del trimestre, pedidos), el dataset puede contener información privilegiada (artículo 7) y operar con él estaría prohibido (artículo 14); el considerando 28 protege los análisis hechos con datos públicos, no los hechos con datos internos de la empresa. Después, las de contrato: si los encuestados tienen obligaciones de confidencialidad con su empleador y cómo las gestiona el proveedor. Las de RGPD son menores si las respuestas se agregan, pero hay que saber si se guardan los nombres de los encuestados. Y las técnicas: desde cuándo existe la encuesta tal como se entrega hoy (point-in-time) y si la historia se ha «limpiado» con las ventas ya publicadas.
Footnotes
Respuesta plegada en cada pregunta
Autoevaluación
Un proveedor de tarjetas entrega en julio de 2023 una historia desde 2018 con la que su señal explica el 39 % de la varianza de las sorpresas de ventas. Con los datos tal como se entregaron cada día, el mismo cálculo da un 28 %. ¿Qué cifra debe usarse para decidir la compra?
- AEl 39 %, porque la historia está más limpia y completa
- BLa media de las dos
- CEl 28 %: es lo que se habría podido medir en cada fecha; la historia está recalibrada con ventas que entonces no se conocían
- DNinguna: el R² no sirve para evaluar datos
Ver respuesta
Respuesta C. El 28 %: es lo que se habría podido medir en cada fecha; la historia está recalibrada con ventas que entonces no se conocían
La historia reconstruida incluye ajustes hechos con información posterior (reponderación al panel actual y calibración con las ventas publicadas). Después de la entrega, el dato en vivo vuelve a dar un 28 %: la diferencia era anticipación, no calidad.
El gasto bruto del panel crece un 47,6 % interanual en el segundo trimestre de 2021 y las ventas un 7,5 %. ¿Qué es lo primero que hay que comprobar?
- ASi el número de tarjetas del panel ha cambiado (en los datos, un banco nuevo añade un 35 % de tarjetas el 2021-03-01)
- BSi el consenso de analistas se ha quedado corto
- CSi hay un error de redondeo en el proveedor
- DSi el trimestre tuvo más días hábiles
Ver respuesta
Respuesta A. Si el número de tarjetas del panel ha cambiado (en los datos, un banco nuevo añade un 35 % de tarjetas el 2021-03-01)
Un panel crece y cambia de bancos o de apps; sin normalizar por tarjetas activas, ese cambio parece demanda. Por tarjeta, el crecimiento es del 7,5 %, el mismo que el de las ventas.
Normalizado por tarjetas, el gasto de las empresas con clientela joven crece más que sus ventas año tras año, y el de las de clientela mayor, menos. ¿Qué sesgo es?
- ASupervivencia de comercios
- BAnticipación por la fecha de entrega
- CError de mapeo de entidades
- DComposición del panel: la fracción de titulares jóvenes sube con el tiempo, así que las marcas que ellos prefieren ganan peso sin vender más
Ver respuesta
Respuesta D. Composición del panel: la fracción de titulares jóvenes sube con el tiempo, así que las marcas que ellos prefieren ganan peso sin vender más
En los datos, la desviación media del gasto por tarjeta frente a las ventas tiene una correlación de 0,98 con la fracción de clientela joven de cada empresa. Un panel no es la población: si su composición cambia, la medida cambia.
Según el considerando 28 de MAR, ¿cuándo pueden los análisis elaborados a partir de datos públicos constituir información privilegiada?
- ANunca: un análisis propio nunca es información privilegiada
- BPor ejemplo, si el mercado espera sistemáticamente su publicación y esta contribuye a la formación de precios; el considerando pide valorar en qué medida la información no es pública
- CSiempre que den beneficios
- DSolo si los elabora un empleado del emisor
Ver respuesta
Respuesta B. Por ejemplo, si el mercado espera sistemáticamente su publicación y esta contribuye a la formación de precios; el considerando pide valorar en qué medida la información no es pública
El considerando 28 dice que esos análisis no deben considerarse en sí mismos información privilegiada, con excepciones. La pregunta de la due diligence es de dónde salen los datos: un dataset que contenga información no pública del emisor (por ejemplo, filtrada por un empleado) es otro caso.
Un proveedor dice que sus datos de tarjetas están «anonimizados» porque sustituye el nombre del titular por un identificador aleatorio. ¿Qué dice el RGPD?
- AQue son datos anónimos y el RGPD no se aplica
- BQue basta con cifrar el identificador
- CQue son datos seudonimizados (se pueden atribuir a una persona con información adicional) y siguen siendo datos personales; solo la información que no permite identificar a nadie queda fuera
- DQue el RGPD solo se aplica a los datos de salud
Ver respuesta
Respuesta C. Que son datos seudonimizados (se pueden atribuir a una persona con información adicional) y siguen siendo datos personales; solo la información que no permite identificar a nadie queda fuera
Artículo 4, apartado 5, y considerando 26 del RGPD. Con 2 compras (comercio y día) de una persona se identifica al 96 % de los titulares de la simulación de la lección: un identificador aleatorio no impide singularizar.
Ideas clave
- Neudata, Eagle Alpha y Nasdaq Data Link cubren partes distintas de la compra (catálogo y evaluaciones, asesoría y cuestionarios de cumplimiento, distribución por API); ninguno sustituye las pruebas del comprador sobre sus propios datos.
- La historia que el proveedor entrega al contratar no es el dato que existía en cada fecha: la de tarjetas del curso está reponderada y calibrada con ventas ya publicadas y lleva el R² de la señal de 0,28 a 0,39. Tras la entrega, el dato en vivo vuelve a 0,28.
- Un panel cambia de tamaño (+35 % de tarjetas el 2021-03-01) y de composición (más titulares jóvenes): se normaliza por tarjetas activas y se mide la deriva de cada empresa frente a sus ventas.
- Supervivencia y retrasos se comprueban en los datos: en el curso, ningún comercio desaparece y cada día llega tres días después, con un 6 % de operaciones todavía por llegar; el trimestre completo está 17 días o más antes de los resultados.
- Legalidad: MAR prohíbe operar con información privilegiada (artículos 7, 8 y 14) y el RGPD se aplica a los datos seudonimizados. Las preguntas al proveedor son de origen, consentimiento y agregación; las respuestas, para el área legal y de cumplimiento.