---
title: "6.7.4 Proveedores y due diligence de datos"
description: "Cómo evaluar un dataset alternativo antes de pagarlo: historia point-in-time frente a backfill, sesgos de panel, retrasos y legalidad (MAR y RGPD)."
url: "https://pythonparatrading.com/curso-python-trading/nivel-6/proveedores-y-due-diligence-de-datos/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-6/proveedores-y-due-diligence-de-datos.png"
---

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

- Curso: [Nivel 6: IA generativa, LLMs, agentes, big data y datos alternativos](https://pythonparatrading.com/curso-python-trading/nivel-6/)
- Módulo: 6.7 · Datos alternativos
- Duración: 60 min
- Requisitos: [1.4.4 · Universos sin sesgo de supervivencia: deslistados, fusiones y cambios de símbolo](https://pythonparatrading.com/curso-python-trading/nivel-1/sesgo-de-supervivencia-y-point-in-time/), [2.5.1 · Sesgos de backtest: anticipación, supervivencia, selección y narrativa](https://pythonparatrading.com/curso-python-trading/nivel-2/sesgos-de-backtest/), [6.7.2 · Tipos de datos alternativos: qué mide cada uno](https://pythonparatrading.com/curso-python-trading/nivel-6/tipos-de-datos-alternativos/)

## Al terminar esta lección

- Situar a los catálogos y distribuidores de datos (Neudata, Eagle Alpha, Nasdaq Data Link) en la compra de un dataset y saber qué preguntas les corresponde responder a ellos y cuáles al comprador.
- Distinguir el dato tal como se entregó (point-in-time) de la historia que el proveedor reconstruye al vender, y medir la inflación in-sample que produce la segunda.
- Detectar en los propios datos los sesgos de un panel: cambios de tamaño, de composición demográfica y de cobertura de comercios, y los retrasos de entrega.
- Plantear las preguntas legales de un dataset con el texto de MAR (Reglamento (UE) 596/2014) y del RGPD, sin confundir la due diligence con asesoramiento jurídico.

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

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

<aside class="callout" data-kind="nota">

Los datos de la lección son los de tarjetas del nivel 6: un proveedor ficticio que entrega cada día el gasto de un
panel de tarjetas en 127 comercios, con el efecto plantado en las ventas de 25 empresas y su verdad publicada. Como
siempre, son sintéticos: lo que se mide aquí enseña a hacer las pruebas, no dice cuánto valen los datos reales.

</aside>

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

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

127 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:

```python
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()}")
```
```output
800 resultados · margen (días): mínimo 17, mediana 36, máximo 62
```

El 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:

```python
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)
```
```output
ú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.001
```

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

<aside class="callout" data-kind="riesgo">

Una historia calibrada con ventas publicadas no se arregla con ningún ajuste del comprador: la información posterior
ya está mezclada en cada cifra. La única defensa es tener el dato point-in-time (archivado por el propio comprador
desde la prueba, o como versiones con fecha que guarda el proveedor) o descontar la historia.

</aside>

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

```python skip
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:

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

Es 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:

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

*Fig. — Crecimiento interanual mediano de 25 empresas: gasto bruto del panel de tarjetas, gasto por tarjeta activa y ventas (2019-2025).* Tres líneas trimestrales. El gasto bruto, destacado, crece entre el 11 % y el 18 % casi todo el periodo y salta al 46-48 % durante cuatro trimestres desde el segundo de 2021, tras la entrada de un banco en el panel el 1 de marzo de 2021 (línea vertical). El gasto por tarjeta y las ventas se mueven juntos entre el 0 % y el 8,5 %.

Durante 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:

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

*Fig. — Deriva del gasto por tarjeta frente a las ventas de cada empresa (media del crecimiento interanual, 2019-2025) según la fracción joven de su clientela.* Dispersión de 25 puntos, uno por empresa, alineados sobre una recta creciente destacada. Las empresas con clientela mayor (fracción joven cerca de 0) tienen una deriva de hasta −1,8 puntos al año; las de clientela joven (cerca de 1), de hasta +2,4. Correlación 0,98.

La 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:

```python
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"])))
```
```output
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: 0
```

En 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-23[^1]; 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.

```python
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%}")
```
```output
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:

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

```python
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))
```
```output
                          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          pendiente
```

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

[^1]: El texto se leyó en las versiones del DOUE que publica el BOE: [MAR](https://www.boe.es/buscar/doc.php?id=DOUE-L-2014-81277)
    y [RGPD](https://www.boe.es/buscar/doc.php?id=DOUE-L-2016-80807), consultadas el 2026-10-23. EUR-Lex publica el
    mismo texto (enlaces en los recursos) y las versiones consolidadas con las modificaciones posteriores.

## Ejercicios

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

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

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

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

```python
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")
```
```output
2 compras: 65.4 % únicos
3 compras: 98.0 % únicos
4 compras: 99.6 % únicos
```

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

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

## Autoevaluación

1. 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?

   - A. El 39 %, porque la historia está más limpia y completa
   - B. La media de las dos
   - 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
   - D. Ninguna: el R² no sirve para evaluar datos

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

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

   - 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)
   - B. Si el consenso de analistas se ha quedado corto
   - C. Si hay un error de redondeo en el proveedor
   - D. Si el trimestre tuvo más días hábiles

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

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

   - A. Supervivencia de comercios
   - B. Anticipación por la fecha de entrega
   - C. Error de mapeo de entidades
   - 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

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

4. Según el considerando 28 de MAR, ¿cuándo pueden los análisis elaborados a partir de datos públicos constituir información privilegiada?

   - A. Nunca: un análisis propio nunca es información privilegiada
   - 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
   - C. Siempre que den beneficios
   - D. Solo si los elabora un empleado del emisor

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

5. 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?

   - A. Que son datos anónimos y el RGPD no se aplica
   - B. Que basta con cifrar el identificador
   - 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
   - D. Que el RGPD solo se aplica a los datos de salud

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

## Recursos

### Documentación

- [Reglamento (UE) 596/2014 sobre el abuso de mercado (MAR), EUR-Lex](https://eur-lex.europa.eu/legal-content/ES/TXT/?uri=CELEX:32014R0596) · eur-lex.europa.eu
- [Reglamento (UE) 2016/679, general de protección de datos (RGPD), EUR-Lex](https://eur-lex.europa.eu/legal-content/ES/TXT/?uri=CELEX:32016R0679) · eur-lex.europa.eu
- [Nasdaq Data Link: cliente de Python (get_table, get_point_in_time)](https://github.com/Nasdaq/data-link-python) · github.com
- [Eagle Alpha: preguntas frecuentes (taxonomía de 16 categorías y 56 subcategorías)](https://www.eaglealpha.com/faqs/) · eaglealpha.com

### Libros

- [de Montjoye, Radaelli, Singh y Pentland (2015): Unique in the shopping mall. Science 347(6221), 536-539. DOI 10.1126/science.1256297](https://doi.org/10.1126/science.1256297) · doi.org

### Datos

- [Datos del nivel 6: tarjetas tal como se entregaron (Parquet)](https://pythonparatrading.com/curso/datos/nivel-6/alt6_tarjetas_pit.parquet) · Descarga · alt6_tarjetas_pit.parquet
- [Datos del nivel 6: historia entregada al contratar (Parquet)](https://pythonparatrading.com/curso/datos/nivel-6/alt6_tarjetas_historico.parquet) · Descarga · alt6_tarjetas_historico.parquet

Lección anterior: [6.7.3 · Web scraping responsable](https://pythonparatrading.com/curso-python-trading/nivel-6/web-scraping-responsable/)

Lección siguiente: [6.7.5 · Del dato alternativo a la señal](https://pythonparatrading.com/curso-python-trading/nivel-6/del-dato-alternativo-a-la-senal/)
