Quant500 Academy

3. Un conjunto de datos auditable de principio a fin

Nivel 6: De los datos a la decisión

Un conjunto de datos auditable es aquel en el que cualquier número de cualquier celda puede rastrearse hasta su origen y hasta la lista exacta de operaciones que lo produjeron. Suena a burocracia hasta el día en que un resultado cambia y no tienes forma de saber si cambió el mundo, el proveedor o tú.

Linaje: la biografía de cada número

El linaje del dato es simplemente eso: la biografía completa de un valor. Sin jerga: si un número de tu tabla no puede contar su vida, no es un dato, es un rumor con formato numérico. Y contar su vida significa cinco cosas concretas:

Hay tres preguntas que ese registro tiene que poder responder sin que nadie recuerde nada. ¿De dónde salió? ¿Qué se le hizo? ¿Qué se decidió sobre él cuando resultó dudoso? Si las tres tienen respuesta escrita, el conjunto es auditable; si alguna depende de tu memoria, no lo es.

El manifiesto que viaja con la tabla

Cada versión del conjunto se acompaña de un fichero de metadatos generado por el mismo proceso que la produce. Nunca escrito a mano después.

import hashlib, json, os, subprocess
import datetime as dt

CLAVE = os.environ.get("CLAVE_PROVEEDOR", "tu_clave_aqui")  # nunca en el manifiesto

def huella(ruta):
    h = hashlib.sha256()
    with open(ruta, "rb") as f:
        for bloque in iter(lambda: f.read(1 << 20), b""):
            h.update(bloque)
    return h.hexdigest()

manifiesto = {
    "conjunto": "panel_sp500_diario",
    "generado_utc": dt.datetime.utcnow().isoformat(timespec="seconds"),
    "commit": subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip(),
    "fuentes": [
        {"campo": "cierre_ajustado", "origen": "proveedor A, endpoint /eod",
         "descargado": "2026-08-03", "fichero": "bruto/eod_2026-08-03.csv.gz",
         "sha256": huella("bruto/eod_2026-08-03.csv.gz")},
        {"campo": "bpa", "origen": "SEC EDGAR, companyfacts",
         "descargado": "2026-08-03", "fichero": "bruto/companyfacts_2026-08-03.zip",
         "sha256": huella("bruto/companyfacts_2026-08-03.zip")},
    ],
    "transformaciones": [
        "1. recortar al calendario oficial de sesiones del mercado",
        "2. aplicar factores de splits y dividendos sobre el precio bruto",
        "3. unir fundamentales por fecha_publicacion con merge_asof backward",
        "4. anular precios con volumen cero",
    ],
    "decisiones": {
        "precios_volumen_cero": "se anulan, no se interpolan; 412 filas",
        "empresas_sin_identificador": "se excluyen y se listan aparte; 7 simbolos",
    },
}
json.dump(manifiesto, open("panel_sp500_diario.manifiesto.json", "w"), indent=2)

La huella criptográfica es la pieza que casi todo el mundo se salta y la única que distingue dos escenarios que se parecen mucho: que tu código haya cambiado o que el proveedor haya reescrito su histórico sin decírtelo. El número de filas no sirve para eso, porque revisar valores no cambia el conteo.

Las transformaciones son una lista ordenada, no un montón

El orden importa y hay que poder recitarlo. Anular precios imposibles antes o después de calcular los factores de ajuste da resultados distintos; filtrar por capitalización antes o después de aplicar la unión con los fundamentales cambia el universo. La regla práctica: el fichero bruto no se toca jamás, se guarda tal cual llegó y todo lo demás se reconstruye desde él con un script que se ejecuta entero de principio a fin. Si tu conjunto solo existe porque un día ejecutaste tres celdas de un cuaderno en cierto orden, no es reproducible.

Las decisiones dudosas se escriben en el momento

Habrá casos que ninguna regla cubra: una empresa que cambia de ejercicio fiscal, un precio que se dispara y podría ser real, una fusión mal reflejada. Cada vez que decidas algo, escribe en el mismo sitio qué criterio aplicaste, sobre qué filas, y qué alternativa descartaste y por qué. Dentro de un año esa nota vale más que el código, porque el código dice qué hiciste y la nota dice por qué.

Y hay un detalle de orden que separa el trabajo serio del resto: esas decisiones se toman y se escriben antes de mirar ningún resultado. En cuanto sabes que anular 412 filas mejora tu rentabilidad y conservarlas la empeora, has dejado de decidir sobre los datos y has empezado a elegir la respuesta que te gusta. El registro de decisiones es, en el fondo, una defensa contra ti mismo, y por eso su valor depende por completo del momento en que se escribe.

Un informe que se genera solo

Y por último, el conjunto tiene que pasar un examen fijo cada vez que se reconstruye.

def informe_auditoria(df, clave=("simbolo", "fecha"), col_precio="cierre_ajustado"):
    fallos, notas = [], []
    notas.append(f"filas {len(df)}  simbolos {df['simbolo'].nunique()}")
    notas.append(f"rango {df['fecha'].min().date()} a {df['fecha'].max().date()}")

    if df.duplicated(list(clave)).any():
        fallos.append("hay claves duplicadas")
    if df["fecha"].max() > pd.Timestamp.today():
        fallos.append("hay fechas en el futuro")
    if not df[col_precio].dropna().gt(0).all():
        fallos.append("hay precios no positivos")

    # Cobertura: un simbolo que desaparece a mitad no genera nulos ni duplicados.
    por_ano = df.groupby(df["fecha"].dt.year)["simbolo"].nunique()
    notas.append("simbolos por ano: " + str(por_ano.to_dict()))

    saltos = df.groupby("simbolo")[col_precio].pct_change().abs()
    notas.append(f"saltos diarios por encima del 50 por ciento: {int((saltos > 0.5).sum())}")
    notas.append(f"nulos en {col_precio}: {df[col_precio].isna().mean():.3%}")

    return {"ok": not fallos, "fallos": fallos, "notas": notas}

Fíjate en la comprobación de cobertura: las filas que faltan no producen nulos ni duplicados, así que ninguna comprobación de valores las ve. Es el hueco por el que se cuela el sesgo de supervivencia del nivel 2. El informe se guarda junto al manifiesto, con la misma fecha, y se compara con el de la ejecución anterior: lo que interesa no es solo que hoy esté bien, sino qué se ha movido desde ayer.

Caso: la reproducción que no cuadra

Hou, Xue y Zhang repitieron unas cuatrocientas cincuenta anomalías publicadas aplicando criterios homogéneos y encontraron que una mayoría amplia, en torno a dos tercios, dejaba de ser estadísticamente significativa. No fue por mala fe ni por errores de programación: la diferencia salía de decisiones que nadie había escrito, como el mínimo de capitalización, la ponderación por tamaño, el tratamiento de los valores extremos y la fecha de rebalanceo. Ese es el argumento entero de este módulo. Si en un conjunto de datos las decisiones no están escritas, ni siquiera tú podrás reproducir tu propio resultado dentro de un año, y no sabrás si la culpa fue del mundo o tuya.

El resto de Quant500

Lo que estás aprendiendo, aplicado al índice todos los días:

La cartera del modelo, cada semana en tu correo

Cada sábado, su semana en un minuto: cómo fue, lo mejor y lo peor, y qué directivos compraron con su dinero. Y el día que el modelo se rehace, qué entra y qué sale. Es una simulación, no un consejo. Como mucho un correo a la semana, sin publicidad y sin cuenta; te das de baja con un clic. Lo hace una persona: Pol Gracia.

Cartera modelo con resultados simulados, no una recomendación personalizada. Tu correo se usa solo para esto: privacidad.

Detrás de la Academia y del resto de Quant500 hay una sola persona: Pol Gracia Serrahima.