Lección 6.4.4 · 70 min

Evidencia y límites de los agentes

¿Opera mejor el modelo que gana los rankings generales? ¿Repite sus decisiones si se le pregunta dos veces? ¿Aguanta en real lo que brilla en el backtest? Esta lección lee tres estudios recientes con sus cifras exactas, mide cuánto se puede concluir de 21 modelos y 50 sesiones, y reproduce los tres problemas con agentes simulados sobre los datos del curso y con el modelo local de 0,5B.

Fig. — LiveTradeBench: rentabilidad de 21 LLM en acciones y en Polymarket (18 de agosto a 24 de octubre de 2025)
Lecciones del nivel 6
  1. 6.1 · NLP financiero clásico y moderno

    1. 6.1.1 Tokenización, embeddings, diccionarios financieros y FinBERT
    2. 6.1.2 Fuentes de texto: EDGAR, CNMV, transcripciones y redes sociales
    3. 6.1.3 Event studies con señales de texto, alineación temporal y topic modeling
  2. 6.2 · LLMs para investigación y trading

    1. 6.2.1 Cómo funciona un LLM: del siguiente token a la API
    2. 6.2.2 Prompting estructurado: JSON que se puede validar
    3. 6.2.3 Guidance, KPIs y tono: qué extrae bien un LLM
    4. 6.2.4 Fine-tuning ligero para tareas financieras: LoRA y QLoRA
    5. 6.2.5 Sesgo de anticipación en LLMs: el modelo que ya conoce el futuro
  3. 6.3 · RAG financiero

    1. 6.3.1 Embeddings y bases vectoriales: FAISS, Chroma, pgvector y Qdrant
    2. 6.3.2 Chunking de documentos financieros: tablas, notas al pie y XBRL
    3. 6.3.3 RAG sobre informes, folletos y KID, y cómo evaluarlo
  4. 6.4 · Agentes de IA para trading

    1. 6.4.1 Agentes con herramientas: tool calling, ReAct y planificación
    2. 6.4.2 Marcos de agentes y MCP
    3. 6.4.3 Arquitecturas multiagente tipo TradingAgents
    4. 6.4.4 Evidencia y límites de los agentes
    5. 6.4.5 Guardarraíles para agentes: el agente propone, el motor decide
    6. 6.4.6 Agentes de investigación: hipótesis, alpha mining y revisión
  5. 6.5 · IA como copiloto del quant

    1. 6.5.1 Asistentes de código con un flujo de tests primero
    2. 6.5.2 Revisión automática de backtests y migraciones de código
    3. 6.5.3 Riesgos del código generado por IA
  6. 6.6 · Big data para trading

    1. 6.6.1 Cuándo los datos son «big»: ticks, libros, opciones y universos
    2. 6.6.2 Parquet, Arrow, Polars en streaming y DuckDB fuera de memoria
    3. 6.6.3 Dask, Spark y Ray: de una máquina a un clúster
    4. 6.6.4 Bases de series temporales: TimescaleDB, QuestDB, ClickHouse, ArcticDB y kdb+
    5. 6.6.5 Almacenamiento en la nube, lakehouse (Delta, Iceberg) y GPU (RAPIDS, cuDF)
  7. 6.7 · Datos alternativos

    1. 6.7.1 Panorama y tamaño del mercado de datos alternativos
    2. 6.7.2 Tipos de datos alternativos: qué mide cada uno
    3. 6.7.3 Web scraping responsable
    4. 6.7.4 Proveedores y due diligence de datos alternativos
    5. 6.7.5 Del dato alternativo a la señal
    6. 6.7.6 Satélite, visión por computador y cadena de suministro

Tres preguntas que un benchmark de agentes debe contestar

Las lecciones 6.4.1 a 6.4.3 construyeron agentes. Esta pregunta qué se sabe de su rendimiento cuando operan, y lo hace con tres preguntas que cualquier estudio de agentes de trading debería contestar antes de enseñar una cifra:

  1. ¿Predice la capacidad general la capacidad para invertir? Si el modelo que gana los rankings de conversación también gana dinero, elegir modelo sería fácil.
  2. ¿Se repite el resultado si se repite la ejecución? Un sistema cuyas decisiones cambian de una ejecución a otra no tiene un rendimiento, tiene una distribución de rendimientos.
  3. ¿Aguanta en real lo que brilla en el backtest? Sin una prueba posterior al entrenamiento del modelo y al diseño de la estrategia, un backtest mide memoria y selección (lecciones 6.2.5 y 2.5.4).

Cada sección lee un estudio que contesta una de ellas, con las cifras tal como aparecen en el PDF (leídos el 2026-10-23), y después reproduce el problema en los datos del curso, donde la respuesta es conocida.

AgenteTras el corteVarias ejecucionesBaselines y costesMuchas sesionesConclusión
Fuente Mermaid
flowchart TB
  A[Agente] --> B[Tras el corte]
  B --> C[Varias ejecuciones]
  C --> D[Baselines y costes]
  D --> E[Muchas sesiones]
  E --> F[Conclusión]

LiveTradeBench: el ranking general no ordena la rentabilidad

«LiveTradeBench: Seeking Real-World Alpha with Large Language Models», de Haofei Yu, Fenghai Li y Jiaxuan You (Universidad de Illinois en Urbana-Champaign), arXiv:2511.03628, versión 1 del 5 de noviembre de 2025. El diseño evita el backtest: los agentes reciben en vivo precios, noticias (titulares y resúmenes) y su cartera, y cada día devuelven una asignación en porcentajes. Hay dos mercados: 15 acciones y ETF de EE. UU. y mercados de predicción de Polymarket. Cada uno de los 21 LLM (Claude, Grok, Qwen, Llama, GPT, Gemini, Kimi y DeepSeek: el texto habla de «six representative model families» y la tabla tiene ocho) va dentro del mismo marco de agente, de tipo ReAct, con el mismo prompt, y la evaluación dura del 18 de agosto al 24 de octubre de 2025, 50 días de negociación según el artículo (en el calendario de la NYSE de los datos del curso, entre esas dos fechas hay 49 sesiones; la diferencia puede venir de cómo cuentan el primer día o del calendario de Polymarket, y no cambia nada de lo que sigue).

Las cifras que cita el temario están en el artículo:

  • «In the stock market, the Spearman correlation between LMArena scores and cumulative returns is only 0.054—virtually no relationship. In Polymarket, the correlation drops to –0.38». La figura siguiente las dibuja.
  • GPT-4.1 «achieves the highest cumulative return rate (> 6%) in the stock market but performs poorly in Polymarket (return < −30%)». En la tabla 2: +6,25 % y −33,69 %.
Fig. — LiveTradeBench: correlación entre la puntuación en LMArena y la rentabilidad de 21 LLM

Y el artículo dice más cosas que conviene leer junto a esas cifras. La correlación del Sharpe entre los dos mercados «is close to zero»: el buen rendimiento en uno no implica nada en el otro. Los autores atribuyen la pérdida de GPT-4.1 en Polymarket a «overreactive allocation changes under higher volatility». En su sección de limitaciones reconocen que el entorno no tiene en cuenta comisiones, horquillas ni liquidez, lo que puede sobrestimar el rendimiento de las estrategias que rebalancean mucho. Los modelos de chat se llaman con temperatura 0,3 y cada uno se evalúa en una sola trayectoria: una evaluación en vivo no se puede repetir. Y la tabla no incluye una cartera pasiva de referencia con los mismos 15 activos. La figura de la cabecera pone los 21 modelos en los dos mercados a la vez: en acciones todos ganan, entre un 1,78 y un 6,25 %; en Polymarket, 17 de 21 pierden.

IN [01]python
import numpy as np
import pandas as pd
from scipy import stats

# Tabla 2 de arXiv:2511.03628 v1: rentabilidad acumulada (%) y Sharpe
ltb = pd.DataFrame([
    ("Claude-Sonnet-3.7", 3.63, 1.45, 20.54, 2.38),
    ("Claude-Sonnet-4", 2.45, 0.72, -40.32, -2.40),
    ("Claude-Opus-4", 3.93, 1.72, -2.04, 0.09),
    ("Claude-Opus-4.1", 3.73, 1.51, -25.69, -3.02),
    ("Grok-3", 3.22, 1.26, -8.35, -0.55),
    ("Grok-4", 4.30, 1.75, 7.38, 1.01),
    ("Qwen2.5-72B", 5.15, 2.18, 1.63, 0.43),
    ("Qwen3-235B-Instruct", 3.52, 1.32, -54.24, -2.97),
    ("Qwen3-235B-Thinking", 1.78, 0.60, -57.62, -1.81),
    ("Llama3.3-70B", 2.72, 0.88, 1.58, 0.40),
    ("Llama4-Scout", 4.65, 1.99, -16.05, -1.18),
    ("Llama4-Maverick", 4.46, 1.65, -18.31, -1.92),
    ("GPT-4o", 3.55, 1.39, -30.96, -3.26),
    ("GPT-4.1", 6.25, 2.64, -33.69, -1.74),
    ("GPT-5", 5.31, 2.19, -23.96, -0.49),
    ("GPT-o3", 6.04, 2.57, -54.84, -3.68),
    ("Gemini-2.5-Flash", 2.10, 0.72, -22.40, -0.82),
    ("Gemini-2.5-Pro", 1.95, 0.61, -35.15, -1.65),
    ("Kimi-K2", 3.07, 1.15, -53.44, -5.26),
    ("DeepSeek-V3.1", 2.46, 0.86, -4.68, -0.07),
    ("DeepSeek-R1", 2.10, 0.78, -13.19, 0.14)],
    columns=["modelo", "cr_acc", "sr_acc", "cr_poly", "sr_poly"])
n = len(ltb)
print(n, "modelos | ganan en acciones:", (ltb.cr_acc > 0).sum(),
      "| ganan en Polymarket:", (ltb.cr_poly > 0).sum())
for a, b in [("cr_acc", "cr_poly"), ("sr_acc", "sr_poly")]:
    print(f"Spearman {a} vs {b}: "
          f"{stats.spearmanr(ltb[a], ltb[b])[0]:.2f}")

def ic_spearman(rho, n):
    """IC del 95 % (Fisher con el error de Fieller: 1,06/raíz(n-3))."""
    z, se = np.arctanh(rho), 1.06 / np.sqrt(n - 3)
    return np.tanh(z - 1.96 * se), np.tanh(z + 1.96 * se)

for rho in (0.054, -0.38):
    t = rho * np.sqrt((n - 2) / (1 - rho**2))
    p = 2 * stats.t.sf(abs(t), n - 2)
    lo, hi = ic_spearman(rho, n)
    print(f"rho = {rho:+.3f}: IC 95 % [{lo:+.2f}, {hi:+.2f}], "
          f"p = {p:.2f}")
OUT [01]La tabla 2 de LiveTradeBench: lo que permiten concluir 21 modelos
21 modelos | ganan en acciones: 21 | ganan en Polymarket: 4
Spearman cr_acc vs cr_poly: 0.17
Spearman sr_acc vs sr_poly: 0.06
rho = +0.054: IC 95 % [-0.41, +0.50], p = 0.82
rho = -0.380: IC 95 % [-0.71, +0.09], p = 0.09

Tres lecturas. Primera: la correlación entre mercados, calculada con la propia tabla, es pequeña tanto en rentabilidad como en Sharpe, como dicen los autores. Segunda: con 21 modelos, el intervalo del 95 % de una correlación de rangos de 0,054 va de −0,41 a 0,50: no descarta una relación moderada en ninguno de los dos sentidos, y la de −0,38 en Polymarket, con p = 0,09, tampoco es significativa al 5 %. La conclusión que sostienen los datos es la del resumen del artículo, «high LMArena scores do not imply superior trading outcomes», no que la capacidad no importe. Tercera: que los 21 ganen en acciones en 50 días dice más del mercado en esas semanas que de los modelos; sin una cartera pasiva en la tabla no hay forma de separar beta de habilidad.

La misma pregunta con respuesta conocida

Con datos reales nadie sabe cuánto ayuda de verdad la capacidad de un modelo. Con los datos del curso se puede fijar. La celda monta un banco de 21 agentes simulados al estilo de LiveTradeBench: las 15 mayores empresas del nivel 6, una decisión al cierre de cada sesión con las noticias publicadas hasta las 16:00 de Nueva York y el rendimiento de 5 sesiones, y una asignación diaria entre las 15 acciones y la caja. Cada agente tiene:

  • una puntuación general simulada (como la de LMArena), entre 1 300 y 1 450;
  • una calidad de lectura de las noticias que crece con la puntuación: la capacidad ayuda de verdad, porque en los datos las noticias mueven el precio (el efecto plantado del README);
  • un apetito por el riesgo (fracción en caja), una agresividad y un gusto por el momentum que no tienen nada que ver con la puntuación;
  • un ruido de inferencia: cada decisión lee la evidencia con un error que cambia en cada ejecución.
IN [02]python
import re

emp = pd.read_csv("acciones6_empresas.csv")
hist = pd.read_csv("acciones6_tickers.csv")
ID = dict(zip(hist["ticker"], hist["id_empresa"]))
ACTUAL = dict(zip(emp["id_empresa"], emp["ticker"]))
px = pd.read_parquet("acciones6_precios.parquet")
cierres = px.pivot(index="fecha", columns="ticker", values="cierre")
acciones = emp.set_index("ticker")["acciones_millones"]
U = list((cierres.loc["2019-01-02"] * acciones).nlargest(15).index)
r = cierres[U].pct_change().loc["2019-01-02":]
F = r.index
R, R5 = r.to_numpy(), cierres[U].pct_change(5).loc[F].to_numpy()

lex = pd.read_csv("texto6_lexico.csv").query("lista == 'financiera'")
POS = set(lex.query("categoria == 'positiva'")["palabra"])
NEG = set(lex.query("categoria == 'negativa'")["palabra"])
def tono(t):
    pal = re.findall(r"\w+", t.lower())
    p, n_ = sum(w in POS for w in pal), sum(w in NEG for w in pal)
    return (p - n_) / (p + n_ + 1)
nt = pd.read_parquet("noticias6_corpus.parquet")
nt["tk"] = nt["ticker"].map(ID).map(ACTUAL)
nt = nt[nt["tk"].isin(U)]
nt["clave"] = nt["titular"].str.replace(
    r"^(?:ACTUALIZA|Última hora|UPDATE|Breaking):\s*", "",
    regex=True, case=False)
nt = nt.sort_values("ts").drop_duplicates(["tk", "clave"])
nt["tono"] = (nt["titular"] + " " + nt["cuerpo"]).map(tono)
ny = nt["ts"].dt.tz_convert("America/New_York").dt.tz_localize(None)
i = np.searchsorted(F + pd.Timedelta(hours=16), ny, side="left")
nt = nt[i < len(F)].assign(dia=F[i[i < len(F)]])
T = (nt.groupby(["dia", "tk"])["tono"].sum().unstack()
     .reindex(index=F, columns=U).fillna(0).to_numpy())

rng = np.random.default_rng(644)
NA = 21
puntuacion = np.round(1300 + 150 * rng.random(NA))
calidad = 0.2 + 0.6 * (puntuacion - 1300) / 150   # sube con la nota
caja = rng.uniform(0.0, 0.6, NA)                   # no depende de ella
kappa = rng.uniform(0.5, 3.0, NA)
gusto_mom = rng.normal(0, 1, NA)

def ejecutar(a, t0, n=50, semilla=0, ruido=1.0):
    """Rentabilidad acumulada del agente `a` en n sesiones desde t0."""
    g = np.random.default_rng([semilla, a, t0])
    v = 1.0
    for t in range(t0, t0 + n):
        lectura = (calidad[a] * T[t - 1]
                   + ruido * g.standard_normal(len(U)))
        s = kappa[a] * (lectura + 6 * gusto_mom[a] * R5[t - 1])
        w = np.exp(s - s.max())
        w = (1 - caja[a]) * w / w.sum()
        v *= 1 + w @ R[t]
    return v - 1

t0 = F.searchsorted(pd.Timestamp("2025-08-18"))
cr = np.array([ejecutar(a, t0) for a in range(NA)])
print(F[t0].date(), "a", F[t0 + 49].date(), "| noticias en",
      f"{(T != 0).mean():.1%} de los días-acción")
print("Spearman(puntuación, rentabilidad):",
      round(stats.spearmanr(puntuacion, cr)[0], 3))
print("rentabilidades (%):", np.round(100 * np.sort(cr), 1))
OUT [02]Un banco de 21 agentes simulados sobre los datos del curso
2025-08-18 a 2025-10-27 | noticias en 9.0% de los días-acción
Spearman(puntuación, rentabilidad): 0.316
rentabilidades (%): [-7.8 -5.3 -4.3 -4.1 -4.  -3.  -2.5 -2.4 -2.3 -1.1 -0.8 -0.7 -0.5 -0.3
  0.   0.2  0.5  1.3  1.6  2.   6.7]

En las mismas fechas que LiveTradeBench, el banco simulado da una correlación de 0,32 entre la puntuación y la rentabilidad, y las rentabilidades de los 21 agentes van del −7,8 al +6,7 %. ¿Es 0,32 la «verdad» del banco? No se puede saber con una ventana: en 50 sesiones, lo que separa a los agentes es cuánto dinero tienen invertido mientras el mercado sube o baja y la suerte de sus lecturas, y la ventaja de leer bien solo aparece cuando hay noticias de esas 15 empresas, que son pocas (un 9 % de los días-acción tiene alguna). Para ver el tamaño del ruido, la celda siguiente repite el banco en todas las ventanas de 50 sesiones sin solapamiento de 2019 a 2025 y lo compara con la correlación a largo plazo, la de la rentabilidad media de todas las ventanas.

IN [03]python
ventanas = list(range(6, len(F) - 50, 50))
CR = np.array([[ejecutar(a, t) for a in range(NA)] for t in ventanas])
rho = np.array([stats.spearmanr(puntuacion, c)[0] for c in CR])
print(len(ventanas), "ventanas | media", round(rho.mean(), 3),
      "| percentiles 5 y 95:", np.round(np.percentile(rho, [5, 95]), 2),
      "| negativas:", f"{(rho < 0).mean():.0%}")
print("a largo plazo: Spearman(puntuación, media) =",
      round(stats.spearmanr(puntuacion, CR.mean(axis=0))[0], 3),
      "| Spearman(caja, media) =",
      round(stats.spearmanr(caja, CR.mean(axis=0))[0], 3))
OUT [03]La correlación de 50 sesiones, ventana a ventana
35 ventanas | media 0.049 | percentiles 5 y 95: [-0.29  0.49] | negativas: 43%
a largo plazo: Spearman(puntuación, media) = 0.373 | Spearman(caja, media) = -0.566
Fig. — Banco simulado de 21 agentes: correlación de rangos entre puntuación y rentabilidad en cada ventana de 50 sesiones

La verdad del banco es positiva: a largo plazo, con las 35 ventanas promediadas, la correlación entre la puntuación y la rentabilidad media es 0,37. Pero en una ventana de 50 sesiones es una tirada: la media de las 35 es solo 0,05, el 90 % central va de −0,29 a 0,49 y un 43 % de las ventanas da una correlación negativa. Y lo que mejor explica la rentabilidad media no es la capacidad sino cuánto invierte cada agente: la correlación de la fracción en caja con la rentabilidad media es −0,57, en un periodo en el que el mercado sube. Un 0,054 o un −0,38 en una sola ventana de 50 días es compatible con casi cualquier relación verdadera.

AlphaForgeBench: la misma pregunta no da la misma respuesta

«AlphaForgeBench: Benchmarking End-to-End Trading Strategy Design with Large Language Models», de Wentao Zhang y otros siete autores (Nanyang Technological University, HKUST (Guangzhou) y Hong Kong Polytechnic University), arXiv:2602.18481, versión 2 del 27 de mayo de 2026, con la cabecera de KDD 2026. Su punto de partida es una crítica a los benchmarks como LiveTradeBench: cuando un LLM emite directamente las acciones de trading, sus decisiones son inestables. En sus palabras, los LLM «produce drastically different trading actions across runs even under deterministic decoding (Section C), rendering single-run live evaluations statistically unreliable».

El experimento del apéndice C es sencillo: BTC con datos diarios de 2025, un prompt con 30 días de precios e indicadores técnicos, una acción por día (comprar todo, mantener o vender todo), sin costes, y cinco ejecuciones idénticas de cada modelo con temperatura 0 y otras cinco con 0,7. Los resultados para gemini-3-pro-preview:

MedidaTemperatura 0Temperatura 0,7
Coincidencia de acciones entre pares de ejecuciones0,36 a 0,480,32 a 0,41
Coincidencia media paso a paso61,4 %—
Pasos con algún desacuerdo entre las cinco95,7 %—
Trayectoriasuna cae cerca de −50 %, otras cerca de cero—

Otros modelos son más estables (gemini-3-flash-preview coincide un 85,3 % con temperatura 0), pero incluso así las curvas de rentabilidad «gradually fan out». Los autores lo atribuyen a la no determinación de la coma flotante y al enrutado de los modelos de mezcla de expertos, amplificados porque los LLM no tienen memoria propia de sus acciones anteriores: una decisión distinta al principio cambia todas las siguientes. Y escriben que en los benchmarks de trading directo «run-to-run variance routinely exceeds intermodel variance». Su propuesta (lección 6.4.6) es que el LLM escriba factores y código de estrategia, que después se ejecutan en un backtest determinista: así la variabilidad queda en un único paso de generación.

Ejecuciones repetidas en el banco simulado

La celda repite cinco veces el banco en la ventana de 2025 con semillas distintas para el ruido de inferencia. Mide la desviación de la rentabilidad de cada agente entre sus ejecuciones, la compara con la desviación entre agentes y calcula cuánto se parecen los rankings de dos ejecuciones. Al final quita el ruido de inferencia (el equivalente a una decodificación de verdad determinista).

IN [04]python
runs = np.array([[ejecutar(a, t0, semilla=s) for a in range(NA)]
                 for s in range(5)])
dentro = runs.std(axis=0).mean()           # mismo agente, 5 tiradas
entre = runs.mean(axis=0).std()            # entre agentes
print(f"desv. dentro de cada agente: {100 * dentro:.2f} pp | "
      f"entre agentes: {100 * entre:.2f} pp")
print("Spearman entre el ranking de la ejecución 0 y las demás:",
      [round(float(stats.spearmanr(runs[0], runs[k])[0]), 2)
       for k in range(1, 5)])
sin_ruido = np.array([[ejecutar(a, t0, semilla=s, ruido=0.0)
                       for a in range(NA)] for s in range(2)])
print("sin ruido de inferencia, ¿ejecuciones idénticas?",
      np.array_equal(sin_ruido[0], sin_ruido[1]))
OUT [04]Variabilidad entre ejecuciones frente a variabilidad entre agentes
desv. dentro de cada agente: 3.58 pp | entre agentes: 2.00 pp
Spearman entre el ranking de la ejecución 0 y las demás: [-0.14, -0.17, 0.17, -0.34]
sin ruido de inferencia, ¿ejecuciones idénticas? True

La desviación de un mismo agente entre sus cinco ejecuciones (3,58 puntos) casi duplica la desviación entre agentes (2,00), y el ranking de una ejecución no predice el de las demás (correlaciones de −0,34 a 0,17): es el patrón que AlphaForgeBench describe en modelos reales. Sin ruido de inferencia el banco se vuelve reproducible, pero el ranking sigue siendo el de una sola ventana de 50 sesiones, con todo el ruido de la sección anterior.

Una decisión sin margen en el modelo local

La decodificación voraz del modelo local del curso es determinista: el mismo texto da siempre la misma respuesta (lección 6.2.1). El problema es otro: el texto cambia por motivos irrelevantes. La celda pregunta al modelo Qwen2.5-0.5B-Instruct si compraría, mantendría o vendería una acción que ha subido un 1,5 % en una semana con una noticia de una demanda, escrito de ocho maneras equivalentes, y mira los logits del primer token de la respuesta: la opción elegida y su margen sobre la segunda.

IN [05]python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

torch.manual_seed(0)
torch.use_deterministic_algorithms(True)
NOMBRE = "Qwen/Qwen2.5-0.5B-Instruct"
tok = AutoTokenizer.from_pretrained(NOMBRE)
llm = AutoModelForCausalLM.from_pretrained(NOMBRE, dtype=torch.float32)

D = "hay una noticia sobre una demanda de un cliente"
variantes = [
    f"La acción ha variado un +1,5 % esta semana y {D}.",
    f"La acción ha variado un +1.5 % esta semana y {D}.",
    f"La acción ha variado un 1,5 % esta semana y {D}.",
    f"La acción ha subido un 1,5 % esta semana y {D}.",
    f"Esta semana la acción ha variado un +1,5 % y {D}.",
    f"La acción ha variado un +1,5 % esta semana; {D}.",
    f"La acción ha variado un +1,50 % esta semana y {D}.",
    f"La acción ha variado un +1,5% esta semana y {D}."]
PIDE = " Responde solo con una palabra: COMPRAR, MANTENER o VENDER."
ids = {o: tok(o, add_special_tokens=False)["input_ids"][0]
       for o in ("COMPRAR", "MANTENER", "VENDER")}
for v in variantes:
    x = tok.apply_chat_template([{"role": "user", "content": v + PIDE}],
                                add_generation_prompt=True,
                                return_tensors="pt", return_dict=True)
    with torch.inference_mode():
        lg = llm(**x).logits[0, -1]
    l = {o: float(lg[i]) for o, i in ids.items()}
    a, b = sorted(l, key=l.get, reverse=True)[:2]
    print(f"{a:8} margen {l[a] - l[b]:.2f} | {v[:46]}")
OUT [05]El formato del prompt decide la operación
COMPRAR  margen 0.05 | La acción ha variado un +1,5 % esta semana y h
VENDER   margen 0.09 | La acción ha variado un +1.5 % esta semana y h
VENDER   margen 0.07 | La acción ha variado un 1,5 % esta semana y ha
VENDER   margen 0.04 | La acción ha subido un 1,5 % esta semana y hay
VENDER   margen 0.34 | Esta semana la acción ha variado un +1,5 % y h
COMPRAR  margen 0.35 | La acción ha variado un +1,5 % esta semana; ha
COMPRAR  margen 0.14 | La acción ha variado un +1,50 % esta semana y
VENDER   margen 0.02 | La acción ha variado un +1,5% esta semana y ha

Con «+1,5 %» el modelo compra; con «+1.5 %», sin el signo, con «subido» en lugar de «variado», con la frase en otro orden o sin espacio antes del «%», vende; con un punto y coma o con «+1,50 %», vuelve a comprar. Los márgenes entre la opción elegida y la siguiente van de 0,02 a 0,35 logits: la «decisión» es casi un empate que resuelve cualquier detalle del texto. Un modelo mayor tendrá márgenes distintos, y la lección no extrapola este 0,5B a los modelos comerciales; lo que sí enseña es qué medir antes de fiarse de un agente: cuánto cambia su decisión ante perturbaciones irrelevantes del prompt, del formato de los datos y del orden de la información. Con un modelo servido por API se suman, además, las fuentes de variación que AlphaForgeBench señala y que el usuario no controla.

Del backtest al real: la degradación de los modelos de deep learning

El temario afirma que los modelos de deep learning fuertes en backtest se degradan en real. La fuente primaria más reciente que lo documenta con una fase en vivo es AlphaCrafter (Yishuo Yuan y otros cuatro autores, Universidad de Nankín, arXiv:2605.05580, versión 2 del 28 de julio de 2026). Su protocolo: carteras diarias sobre el CSI 300 y el S&P 500, entrenamiento de 2016 a 2022, validación en 2023, backtest de enero de 2024 a febrero de 2026 (519 y 542 sesiones) y una fase en vivo con la API de paper trading de un bróker real de marzo a junio de 2026 (69 y 73 sesiones), posterior al corte de entrenamiento de todos los LLM que usan. Su texto: «Machine learning and deep learning methods, notably LSTM, demonstrate strong backtesting performance but exhibit pronounced degradation in live trading, exposing their vulnerability to overfitting and regime shifts. This pattern is consistent across both markets».

IN [06]python
# Tabla 2 de arXiv:2605.05580 v2: Sharpe anualizado, métodos sin LLM
ac = pd.DataFrame([
    ("MACD", 0.99, 0.87, 0.72, 1.22),
    ("Grid", 0.33, -0.26, 1.11, 0.39),
    ("LightGBM", 1.22, 0.51, 0.51, 1.19),
    ("XGBoost", 1.24, 0.36, -0.16, 1.01),
    ("LSTM", 1.43, 0.35, 1.38, 0.83),
    ("Transformer", 1.19, -0.32, 0.44, 0.28),
    ("TRA", 1.16, 0.71, 1.01, 1.02)],
    columns=["metodo", "csi_bt", "csi_vivo", "sp_bt", "sp_vivo"])
SES = {"csi": (519, 69), "sp": (542, 73)}       # sesiones bt / vivo
se = lambda n: np.sqrt(252 / n)                 # error típico del SR
for m, (nb, nv) in SES.items():
    d = ac[f"{m}_vivo"] - ac[f"{m}_bt"]
    s = np.hypot(se(nb), se(nv))
    print(f"{m}: bajan {(d < 0).sum()} de 7 | error típico del SR: "
          f"bt {se(nb):.2f}, vivo {se(nv):.2f}, diferencia {s:.2f}")
ac.set_index("metodo")
OUT [06]Backtest frente a real en AlphaCrafter, con su error típico
csi: bajan 7 de 7 | error típico del SR: bt 0.70, vivo 1.91, diferencia 2.03
sp: bajan 3 de 7 | error típico del SR: bt 0.68, vivo 1.86, diferencia 1.98
             csi_bt  csi_vivo  sp_bt  sp_vivo
metodo
MACD           0.99      0.87   0.72     1.22
Grid           0.33     -0.26   1.11     0.39
LightGBM       1.22      0.51   0.51     1.19
XGBoost        1.24      0.36  -0.16     1.01
LSTM           1.43      0.35   1.38     0.83
Transformer    1.19     -0.32   0.44     0.28
TRA            1.16      0.71   1.01     1.02
Fig. — AlphaCrafter: Sharpe de backtest (gris) y en vivo (azul) de siete métodos sin LLM, CSI 300 y S&P 500

La lectura cuidadosa tiene dos partes. La que apoya al temario: en el CSI 300 los siete métodos sin LLM bajan del backtest al real, el LSTM pasa de 1,43 a 0,35 y el Transformer de 1,19 a −0,32; en el S&P 500 el LSTM también baja (de 1,38 a 0,83). La que obliga a matizar: en el S&P 500 solo bajan tres de siete; MACD, LightGBM y XGBoost suben con claridad en real y TRA queda igual, y el error típico de un Sharpe anualizado medido en unas 70 sesiones es de casi 1,9, así que ninguna caída concreta es distinguible del ruido; la diferencia entre backtest y real tiene un error típico de unos 2. Además, los autores proponen un método competidor, que en su tabla se degrada menos. El patrón es coherente con lo que el curso ya ha medido por otras vías: el sobreajuste de la validación (lección 5.5.4), la deriva (lección 5.10.2), el decaimiento de las anomalías publicadas (lección 5.1.1) y el hundimiento de los Sharpe de los modelos fundacionales con costes (lección 5.8.4). Pero una ventana en vivo de tres meses no lo prueba por sí sola.

Dos estudios más completan el cuadro de los agentes con LLM. FINSABER (Li, Kim, Cucuringu y Ma, arXiv:2505.07078) repite estrategias de timing con LLM durante dos décadas y con más de 100 acciones, y encuentra que las ventajas publicadas «deteriorate significantly under broader cross-section and over a longer-term evaluation»; las estrategias son demasiado conservadoras en mercados alcistas y demasiado agresivas en bajistas. DeepFund (Li et al., arXiv:2505.11065) evalúa nueve modelos en vivo, con datos posteriores al corte de cada uno, porque los backtests permiten a los LLM «time travel», y encuentra que incluso DeepSeek-V3 y Claude-3.7-Sonnet tienen pérdidas netas.

Cómo evaluar un agente sin engañarse

PreguntaSeñal de alarmaQué pedir
¿Cuándo se entrenó el modelo?Backtest anterior al cortePeriodo posterior al corte efectivo, en vivo o en paper trading (lección 6.2.5)
¿Cuántas ejecuciones?UnaVarias semillas o repeticiones y la distribución del resultado
¿Contra qué se compara?Reglas técnicas o nadaCartera pasiva con los mismos activos y la misma información combinada sin LLM
¿Costes?No se mencionanComisiones, horquilla y deslizamiento con la rotación real (lección 2.4.7)
¿Cuántas sesiones y modelos?50 días, 21 modelosError típico menor que la diferencia que se quiere detectar
¿Cuántas configuraciones se probaron?No se diceDeflated Sharpe (lección 2.5.4)
¿Es estable la decisión?No se mideSensibilidad a perturbaciones irrelevantes del prompt y de los datos

Errores típicos

  • Elegir el modelo por un ranking general. En LiveTradeBench la correlación con la rentabilidad fue de 0,054 en acciones y de −0,38 en Polymarket.
  • Leer una correlación de 21 puntos como si fuera precisa. Su intervalo del 95 % mide casi 0,9 de ancho.
  • Publicar una sola ejecución. La variabilidad de un agente entre ejecuciones puede superar a la que hay entre agentes.
  • Confiar en la temperatura 0. Fija la decodificación, no el texto de entrada, ni la precisión numérica, ni el servidor que responde.
  • Tomar tres meses en vivo como prueba. El error típico de un Sharpe en 70 sesiones es de casi 1,9.
  • Olvidar la beta. Si todos los agentes ganan en un mercado que sube, nadie ha demostrado habilidad.

Ejercicios

  1. ¿Cuántos modelos haría falta comparar para que una correlación de Spearman verdadera de 0,18 (la del banco simulado a largo plazo) se detecte con una potencia del 80 % al 5 % bilateral? Usa la aproximación de Fisher con el error de Fieller.
Solución
IN [07]python
def modelos_necesarios(rho, alfa=0.05, potencia=0.8):
    z = stats.norm.ppf(1 - alfa / 2) + stats.norm.ppf(potencia)
    return int(np.ceil((1.06 * z / np.arctanh(rho)) ** 2 + 3))

for rho in (0.18, 0.3, 0.5):
    print(rho, "->", modelos_necesarios(rho), "modelos")
OUT [07]Potencia de una correlación de rangos
0.18 -> 270 modelos
0.3 -> 96 modelos
0.5 -> 33 modelos

Para una correlación verdadera de 0,18 harían falta del orden de 270 modelos, más de diez veces los de LiveTradeBench; para 0,5, unos 33. Y eso con el ruido de una ventana fija: en el banco simulado, gran parte de la dispersión viene de la ventana de 50 sesiones, así que además harían falta más ventanas. Un benchmark de agentes con unas decenas de modelos solo puede detectar relaciones fuertes.

  1. Repite el banco simulado con ventanas de 250 sesiones (un año) en lugar de 50. ¿Cómo cambian la media y los percentiles 5 y 95 de la correlación?
Solución
IN [08]python
filas = []
for t in range(6, len(F) - 250, 250):
    c = [ejecutar(a, t, n=250) for a in range(NA)]
    filas.append({"inicio": F[t].date(),
                  "mercado_%": 100 * (np.prod(1 + R[t:t + 250]
                                              .mean(axis=1)) - 1),
                  "rho_puntuacion": stats.spearmanr(puntuacion, c)[0],
                  "rho_caja": stats.spearmanr(caja, c)[0]})
v = pd.DataFrame(filas).set_index("inicio")
print("media de rho_puntuacion:", round(v["rho_puntuacion"].mean(), 3))
v.round(2)
OUT [08]Ventanas de un año
media de rho_puntuacion: 0.027
            mercado_%  rho_puntuacion  rho_caja
inicio
2019-01-10      17.87            0.07     -0.10
2020-01-08      12.65            0.25     -0.34
2021-01-05      17.37            0.03     -0.06
2021-12-31     -26.11            0.09      0.56
2022-12-29      10.51           -0.13     -0.15
2023-12-28      26.44            0.06     -0.33
2024-12-26       6.44           -0.20     -0.29

Con ventanas de un año caben siete en los datos, y la correlación tampoco se estabiliza: va de −0,20 a 0,25, con una media de 0,03, lejos del 0,37 a largo plazo. La columna de la caja lo explica: en la ventana que empieza a final de 2021, con el mercado de las 15 empresas cayendo un 26 %, la correlación de la caja con la rentabilidad es 0,56. Alargar la ventana no basta cuando la habilidad es pequeña frente al ruido de inferencia, que se acumula durante todo el año, y frente a la exposición al mercado: en un año bajista los agentes con más caja ganan, sea cual sea su capacidad. La relación verdadera solo aparece promediando muchos periodos distintos, y aun así con pocos agentes (ejercicio 1) el error es grande.

  1. Sin código: diseña la evaluación de un agente de research que propone cada semana compras y ventas sobre 50 acciones. Tienes presupuesto para un año de llamadas a un modelo comercial.
Solución

Primero, fechas: solo semanas posteriores al corte efectivo del modelo, en paper trading o en sombra (lección 4.5.3), y nada de backtest anterior al corte salvo para depurar. Segundo, repetición: cada semana, varias ejecuciones independientes de la misma decisión (por ejemplo, cinco) para medir la estabilidad y usar la decisión mayoritaria; el presupuesto se reparte entre ejecuciones y sesiones, no se gasta en una sola tirada larga. Tercero, referencias calculadas con la misma información sin LLM: la cartera pasiva de las 50 acciones y una regla simple con las mismas señales (lección 6.4.3). Cuarto, costes con la rotación real. Quinto, perturbaciones: el mismo prompt con cambios irrelevantes (formato de números, orden de las noticias) en una muestra de decisiones, para estimar cuánto cambia la decisión. Y al final, la cuenta honesta: con 52 semanas, el error típico de un Sharpe anualizado es de 1; el año servirá para descartar agentes malos o inestables, no para demostrar que uno es bueno.

Respuesta plegada en cada pregunta

Autoevaluación

  1. En LiveTradeBench, la correlación de Spearman entre la puntuación de LMArena y la rentabilidad acumulada en acciones de 21 modelos es 0,054. ¿Qué se puede concluir?

    • AQue en esa ventana de 50 días el ranking general no ordenó la rentabilidad; con 21 modelos, el intervalo del 95 % de esa correlación va aproximadamente de −0,41 a 0,50, así que tampoco descarta una relación moderada
    • BQue la capacidad general de un modelo no tiene ninguna relación con su capacidad para invertir
    • CQue los modelos con más puntuación pierden dinero
    • DQue hay que elegir el modelo con menos puntuación en LMArena
    Ver respuesta

    Respuesta A. Que en esa ventana de 50 días el ranking general no ordenó la rentabilidad; con 21 modelos, el intervalo del 95 % de esa correlación va aproximadamente de −0,41 a 0,50, así que tampoco descarta una relación moderada

    Con n = 21 el error típico de una correlación de rangos es del orden de 0,25. La conclusión de los autores, que una puntuación alta no implica mejores resultados, se sostiene; la de que no hay ninguna relación, no. La simulación de la lección muestra cómo una relación positiva verdadera produce en 50 sesiones correlaciones de −0,29 a 0,49 la mayor parte de las veces.

  2. GPT-4.1 tuvo la rentabilidad más alta en acciones (6,25 %) y perdió un 33,69 % en Polymarket. ¿Qué lectura apoya el propio artículo?

    • AQue GPT-4.1 es el mejor modelo para invertir en acciones
    • BQue Polymarket es un mercado ineficiente para cualquier modelo
    • CQue el resultado en un mercado no se generaliza al otro: la correlación del Sharpe entre los dos mercados es casi nula, y los autores atribuyen la pérdida a cambios de cartera exagerados con más volatilidad
    • DQue el artículo descuenta comisiones y por eso las cifras son fiables
    Ver respuesta

    Respuesta C. Que el resultado en un mercado no se generaliza al otro: la correlación del Sharpe entre los dos mercados es casi nula, y los autores atribuyen la pérdida a cambios de cartera exagerados con más volatilidad

    Los autores escriben que el rendimiento en un mercado no generaliza al otro y que GPT-4.1 rindió mal en Polymarket probablemente por cambios de asignación exagerados. Además, el artículo reconoce que no tiene en cuenta comisiones, horquillas ni liquidez, y en 50 días las 21 carteras de acciones ganaron entre un 1,78 y un 6,25 %.

  3. AlphaForgeBench repite cinco veces la misma tarea de trading de BTC con temperatura 0. ¿Qué encuentra en gemini-3-pro-preview?

    • AQue con temperatura 0 las cinco ejecuciones son idénticas
    • BUna coincidencia entre pares de ejecuciones de solo 0,36 a 0,48 en las acciones, y trayectorias tan distintas que una ejecución cae cerca de un −50 % mientras otras quedan cerca de cero
    • CQue la temperatura no influye en nada
    • DQue el modelo acierta siempre la dirección del BTC
    Ver respuesta

    Respuesta B. Una coincidencia entre pares de ejecuciones de solo 0,36 a 0,48 en las acciones, y trayectorias tan distintas que una ejecución cae cerca de un −50 % mientras otras quedan cerca de cero

    Los autores atribuyen la variabilidad con decodificación nominalmente determinista a la no determinación de la coma flotante y al enrutado de los modelos de mezcla de expertos, amplificada porque una acción distinta al principio cambia todas las siguientes. Por eso proponen evaluar el código de estrategias que el LLM genera, que después se ejecuta de forma determinista.

  4. En AlphaCrafter, el LSTM pasa en el S&P 500 de un Sharpe de 1,38 en backtest (542 sesiones) a 0,83 en vivo (73 sesiones). ¿Qué hay que añadir antes de concluir?

    • ANada: la caída prueba que el LSTM está sobreajustado
    • BQue el LSTM funciona mejor en vivo que en backtest
    • CQue el resultado demuestra que el deep learning no sirve en finanzas
    • DQue un Sharpe anualizado medido en 73 sesiones tiene un error típico de casi 1,9, así que esa caída concreta cabe en el ruido; el patrón es más informativo (en el CSI 300 bajan los siete métodos sin LLM) y es coherente con otras pruebas de sobreajuste
    Ver respuesta

    Respuesta D. Que un Sharpe anualizado medido en 73 sesiones tiene un error típico de casi 1,9, así que esa caída concreta cabe en el ruido; el patrón es más informativo (en el CSI 300 bajan los siete métodos sin LLM) y es coherente con otras pruebas de sobreajuste

    El error típico del Sharpe anualizado es aproximadamente la raíz de 252 entre el número de sesiones: 1,86 con 73. Una sola ventana en vivo de tres meses no basta para medir la degradación de un método; varias señales en la misma dirección, con un mecanismo conocido (sobreajuste y cambio de régimen), sí apuntan a ella.

  5. En la lección, el modelo local de 0,5B decide COMPRAR o VENDER ante la misma información según se escriba «+1,5 %» o «+1.5 %». ¿Qué indica?

    • AQue el modelo no entiende la coma decimal
    • BQue hay que escribir siempre los números con punto
    • CQue su decisión se juega en diferencias de centésimas entre los logits de las dos opciones: cualquier cambio irrelevante del texto la invierte, y una decisión así no puede ser la base de una orden
    • DQue la decodificación voraz es aleatoria
    Ver respuesta

    Respuesta C. Que su decisión se juega en diferencias de centésimas entre los logits de las dos opciones: cualquier cambio irrelevante del texto la invierte, y una decisión así no puede ser la base de una orden

    En los ocho formatos de la lección, la diferencia entre el logit de la primera opción y el de la segunda va de 0,02 a 0,35, y el modelo elige COMPRAR en tres y VENDER en cinco. La decodificación voraz es determinista para un texto dado, pero el texto mismo cambia por motivos irrelevantes. Un modelo mayor tendrá márgenes distintos, pero el problema de fondo, decisiones sin margen, se mide igual.

Ideas clave

  • LiveTradeBench (Yu, Li y You, arXiv:2511.03628): 21 LLM en vivo del 18 de agosto al 24 de octubre de 2025; Spearman entre LMArena y la rentabilidad, 0,054 en acciones y −0,38 en Polymarket; GPT-4.1, +6,25 % en acciones y −33,69 % en Polymarket; sin costes de transacción.
  • Con 21 modelos una correlación de rangos tiene un error típico de unos 0,25: 0,054 no distingue «nada» de «algo moderado». Lo que sí queda claro es que el ranking general no ordena los resultados.
  • AlphaForgeBench (arXiv:2602.18481) documenta acciones «drastically different» entre ejecuciones del mismo modelo con decodificación determinista: un backtest de una ejecución no es reproducible.
  • AlphaCrafter (arXiv:2605.05580) observa que los métodos de deep learning, en especial el LSTM, rinden bien en backtest y se degradan en vivo; con 69-73 sesiones en vivo cada caída cabe en el ruido, pero el patrón se repite.
  • En un banco simulado donde la capacidad ayuda de verdad, 50 sesiones dan correlaciones entre −0,29 y 0,49 y la variabilidad de un mismo agente entre ejecuciones casi duplica la que hay entre agentes.
  • Evaluar un agente exige periodos posteriores al corte del modelo, varias ejecuciones, baselines pasivas, costes y bastantes sesiones para que el error típico sea menor que la diferencia que se quiere medir.

Recursos