Lección 6.4.3 · 75 min
Arquitecturas multiagente tipo TradingAgents
TradingAgents reparte la decisión de inversión entre analistas, investigadores que debaten, un trader y un equipo de riesgo, como una mesa real. Esta lección lee el artículo con sus cifras y sus límites, monta la misma arquitectura sobre los datos del curso con agentes simulados y la mide con honestidad: contra el mercado, contra cada señal sola y contra la regla determinista a la que el debate converge, con costes.
- Módulo
- 6.4 · Agentes de IA para trading
- Duración
- 75 min
- Requisitos
- 6.4.2 · Marcos de agentes y MCP, 6.4.1 · Agentes con herramientas: tool calling, ReAct y planificación, 6.1.3 · Event studies con señales de texto, alineación temporal y topic modeling, 2.4.7 · Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos, 2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)
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
Una mesa de inversión hecha de agentes
La lección 6.4.2 construyó un flujo con un solo redactor. La idea de las arquitecturas multiagente es repartir el trabajo entre agentes con papeles distintos, como en una mesa de inversión: unos recogen datos y escriben informes, otros discuten, alguien decide y alguien controla el riesgo. La referencia más citada es TradingAgents, de Yijia Xiao, Edward Sun, Di Luo y Wei Wang (UCLA, MIT y Tauric Research), arXiv:2412.20138; lo que sigue sale del PDF de la versión 7 (3 de junio de 2025), leído el 2026-10-23. Su organización tiene cinco piezas:
- Equipo de analistas: cuatro agentes trabajan a la vez, cada uno con sus herramientas: fundamental (estados financieros, informes de resultados, operaciones de directivos), sentimiento (publicaciones en redes sociales y puntuaciones de sentimiento), noticias (noticias, anuncios de gobiernos e indicadores macroeconómicos) y técnico (indicadores como MACD y RSI, precios y volúmenes). Cada uno escribe un informe estructurado.
- Equipo de investigación: un investigador alcista y uno bajista leen los informes y debaten en lenguaje natural durante n rondas; un facilitador revisa el debate y elige la perspectiva que prevalece.
- Trader: convierte el análisis en una decisión (comprar, vender o mantener) con su justificación.
- Equipo de gestión del riesgo: tres agentes, uno propenso al riesgo, uno neutral y uno conservador, discuten n rondas y ajustan el plan del trader a los límites de riesgo.
- Gestor del fondo: revisa esa discusión, decide los ajustes y aprueba la operación.
Fuente Mermaid
flowchart TB A[Fundamental] --> R[Alcista vs bajista] B[Sentimiento] --> R C[Noticias] --> R D[Técnico] --> R R --> F[Facilitador] F --> T[Trader] T --> G[Equipo de riesgo] G --> M[Gestor del fondo]
Dos decisiones de diseño del artículo merecen atención. Los agentes se comunican sobre todo con documentos
estructurados guardados en un estado global, no con conversaciones largas, para no perder información en un
«teléfono roto»; solo los debates usan diálogo. Y reparten los modelos: modelos rápidos (gpt-4o-mini, gpt-4o) para
resumir y recuperar datos, y modelos de razonamiento (o1-preview) para analizar y decidir. El código público
(repositorio TauricResearch/TradingAgents, licencia Apache-2.0) está construido con LangGraph (lección 6.4.2), deja
configurar el número de rondas de debate y de riesgo y avisa en su README de que está pensado para investigación y de
que el rendimiento puede variar con el modelo, la temperatura, el periodo, los datos «and other non-deterministic
factors».
Lo que mide el artículo, y lo que no
La evaluación original es un backtest del 1 de enero al 29 de marzo de 2024 sobre acciones tecnológicas grandes, con datos de precios, noticias, redes sociales, operaciones de directivos, estados financieros y 60 indicadores técnicos. Las comparaciones son con cinco reglas: comprar y mantener, MACD, KDJ con RSI, ZMR y SMA. La tabla 1 muestra tres acciones:
| Acción | TradingAgents: rend. acumulado | Sharpe | Comprar y mantener: rend. acumulado |
|---|---|---|---|
| AAPL | 26,62 % | 8,21 | −5,23 % |
| GOOGL | 24,36 % | 6,39 | 7,78 % |
| AMZN | 23,21 % | 5,60 | 17,1 % |
Antes de leer esas cifras como una ventaja hay que poner al lado lo que el propio artículo dice y lo que no dice:
- Tres meses. Una nota al pie explica que el backtest dura tres meses «due to intensive LLM and tool use (11 LLM calls & 20+ tool calls/prediction)», y que el Sharpe más alto «exceeds our expected empirical range»; los autores lo atribuyen a que hubo pocos retrocesos en ese periodo. Un Sharpe anualizado medido en unas 60 sesiones tiene un error típico de más de 2 puntos.
- Sin costes declarados. El texto no menciona comisiones, horquillas ni deslizamiento.
- Sesgo de anticipación posible. Los modelos usados se entrenaron con textos de Internet; el artículo afirma que los agentes solo ven datos hasta cada día, pero no discute si los modelos conocían por su entrenamiento lo que pasó en esas acciones (lección 6.2.5).
- Baselines débiles. Reglas técnicas de una sola acción, no carteras ni modelos cuantitativos.
Hay una evaluación independiente posterior, con todas las cautelas porque sus autores proponen un método competidor: AlphaCrafter (Yuan et al., arXiv:2605.05580, versión 2 de julio de 2026) reimplementa TradingAgents en carteras del CSI 300 y del S&P 500 con backtest de enero de 2024 a febrero de 2026 y una fase en vivo de marzo a junio de 2026 con una API de paper trading. Con Claude Opus 4.6 como modelo, TradingAgents pasa en el CSI 300 de un Sharpe de 1,37 en el backtest a 0,67 en vivo, y en el S&P 500 de 1,08 a 1,25; los autores describen a los agentes de «role-playing» como inestables según el modelo usado. Lejos, en todo caso, de un Sharpe de 8.
Los cuatro analistas sobre los datos del curso
Para medir una arquitectura así hay que saber qué señal hay en los datos. Los datos sintéticos del nivel 6 lo permiten: las 120 empresas tienen efectos plantados con parámetros publicados en el README. La lección monta los cuatro analistas con datos point-in-time y una decisión por semana, al cierre del último día de cada semana, de enero de 2019 a diciembre de 2025:
| Analista | Datos | Señal | Efecto plantado (README) |
|---|---|---|---|
| Fundamental | panel5_features: valor contable / precio y SUE | g(valor_bp) + g(sue)·e^(−días/45) | Valor y sorpresa de resultados (nivel 5) |
| Técnico | panel5_features: momentum 12-1 y rendimiento de la semana | g(mom_12_1) − g(ret_1s) | Momentum (con caídas en rebotes) y reversión semanal |
| Noticias | Noticias sin copias hasta las 16:00 NY | Suma del tono de diccionario de la semana | Reacción y deriva de 5 sesiones |
| Sentimiento | Publicaciones con cashtag de cuentas de más de 60 días | Recuento de palabras alcistas y bajistas | 10 pb al día siguiente por unidad de opinión, AR(1) diario 0,7 |
g(·) es el rango normal transversal de cada semana. Cada publicación se atribuye a la empresa de su cashtag (con
el ticker vigente en su fecha, que la tabla de tickers traduce al actual). Las de cuentas de 60 días o menos se
excluyen porque las cuentas automatizadas del generador son recientes y con pocos seguidores: filtrar por antigüedad es
una heurística, no la verdad del README (entre las cuentas excluidas hay un 41 % de bots; entre las que quedan, un
16 %).
import re
import numpy as np
import pandas as pd
from scipy import stats
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"]))
TK = sorted(emp["ticker"])
px = pd.read_parquet("acciones6_precios.parquet")
cierres = px.pivot(index="fecha", columns="ticker",
values="cierre")[TK]
f5 = pd.read_parquet("panel5_features.parquet", columns=[
"fecha", "ticker", "mom_12_1", "ret_1s", "valor_bp", "sue",
"dias_desde_informe"])
f5 = f5[f5["ticker"].isin(TK) & (f5["fecha"] >= "2019-01-01")]
FECHAS = pd.DatetimeIndex(sorted(f5["fecha"].unique()))
sem = cierres.reindex(FECHAS)
r_sig = sem.shift(-1) / sem - 1 # semana siguiente
CORTES = FECHAS + pd.Timedelta(hours=16) # cierre de Nueva York
def g(df):
"""Rango normal transversal de cada fecha (filas)."""
rk = df.rank(axis=1).sub(0.5)
return pd.DataFrame(stats.norm.ppf(rk.div(df.count(axis=1),
axis=0)),
index=df.index, columns=df.columns)
def semana_de(ts):
"""Primera fecha de decisión cuyo cierre es posterior a `ts`."""
ny = ts.dt.tz_convert("America/New_York").dt.tz_localize(None)
i = np.searchsorted(CORTES, ny, side="left")
return pd.Series(FECHAS[i.clip(0, len(FECHAS) - 1)],
index=ts.index).where(i < len(FECHAS))
print(len(FECHAS), "semanas,", FECHAS[0].date(), "a",
FECHAS[-1].date(), "|", len(TK), "empresas")366 semanas, 2019-01-04 a 2025-12-31 | 120 empresaspiv = lambda c: f5.pivot(index="fecha", columns="ticker",
values=c)[TK]
fun = g(piv("valor_bp")) + g(piv("sue")) * np.exp(
-piv("dias_desde_informe") / 45)
tec = g(piv("mom_12_1")) - g(piv("ret_1s"))
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")
PREF = r"^(?:ACTUALIZA|Última hora|UPDATE|Breaking):\s*"
nt["clave"] = nt["titular"].str.replace(PREF, "", regex=True,
case=False)
nt = nt.sort_values("ts").drop_duplicates(["ticker", "clave"])
nt["tono"] = (nt["titular"] + " " + nt["cuerpo"]).map(tono)
nt["semana"] = semana_de(nt["ts"])
nt["tk"] = nt["ticker"].map(ID).map(ACTUAL)
noti = g(nt.groupby(["semana", "tk"])["tono"].sum().unstack()
.reindex(index=FECHAS, columns=TK)).fillna(0.0)
so = pd.read_parquet("social6_posts.parquet")
so["tk"] = (so["texto"].str.extract(r"\$([A-Z]{4})")[0]
.map(ID).map(ACTUAL))
so = so[so["antiguedad_cuenta_dias"] > 60] # fuera cuentas nuevas
ALC = ["bien", "cargando", "rompe", "gusta", "compra", "aguantando",
"sorpresa", "buenos", "strong", "loading", "breaking out",
"holding", "buy", "higher"]
BAJ = ["pique", "vendido", "cuidado", "pierde", "castigo", "levanta",
"weak", "shorting", "sold", "dead cat", "breaking down",
"avoid", "bleeding"]
tl = so["texto"].str.lower()
cuenta = lambda ws: sum(tl.str.contains(w, regex=False).astype(int)
for w in ws)
so["s"] = (cuenta(ALC) - cuenta(BAJ)).clip(-1, 1)
so["semana"] = semana_de(so["ts"])
soc = g(so.groupby(["semana", "tk"])["s"].mean().unstack()
.reindex(index=FECHAS, columns=TK)).fillna(0.0)
SEN = {"fundamental": fun, "tecnico": tec, "noticias": noti,
"sentimiento": soc}
def ic_semanal(sig):
a = sig.iloc[:-1].rank(axis=1)
b = r_sig.iloc[:-1].rank(axis=1)
a, b = a.sub(a.mean(axis=1), axis=0), b.sub(b.mean(axis=1), axis=0)
with np.errstate(invalid="ignore", divide="ignore"):
v = (a * b).sum(axis=1) / np.sqrt((a**2).sum(axis=1)
* (b**2).sum(axis=1))
v = v.dropna()
return v.mean(), v.mean() / v.std() * np.sqrt(len(v))
tabla = pd.DataFrame({k: {"IC": ic_semanal(v)[0],
"t": ic_semanal(v)[1],
"cobertura": (v != 0).mean().mean()}
for k, v in SEN.items()}).T
tabla.round(3) IC t cobertura
fundamental 0.016 3.352 1.000
tecnico 0.010 1.552 0.992
noticias 0.013 2.771 0.355
sentimiento 0.018 3.772 0.520Las cuatro señales tienen el signo de su efecto plantado y un IC semanal pequeño, como corresponde a datos con relación señal/ruido realista: el mejor, el sentimiento de redes, ronda 0,018, y el técnico, 0,010 con un t que no llega a 2 porque el momentum del generador sufre caídas en los rebotes. Las de noticias y redes solo cubren las semanas con publicaciones (un 35 % y un 52 % de las empresas-semana); el resto se queda en cero, «sin datos». Ningún agente, por bien que escriba, puede transmitir más información que la que trae su señal: estos números son el techo de cada analista.
Agentes simulados: leer con ruido, debatir, decidir
Ahora la arquitectura. Para ejecutarla miles de veces sin un LLM, cada agente se simula con una regla que imita lo que hace un LLM con un informe:
- Analista: lee su señal con un error de lectura (normal, desviación 0,6) y la convierte en una nota entera de −2 (venta fuerte) a +2 (compra fuerte), como el «rating» que pide un prompt. Sin datos, nota 0.
- Debate: en cada ronda, el investigador alcista suma las notas positivas y el bajista las negativas; a partir de la segunda ronda vuelven a leer los informes, con un error nuevo (un LLM no lee dos veces igual un texto largo). El facilitador promedia el saldo de las rondas: el veredicto.
- Trader: compra si el veredicto llega a +0,75, vende si baja de −0,75 y si no, no opera.
- Gestor de riesgo (la postura conservadora, que es la que el gestor del fondo aprueba aquí): no opera cuando la desviación de las cuatro notas llega a 1,25 (desacuerdo alto). La cartera pone el 50 % del capital en las compras y el 50 % en las ventas, a partes iguales.
Fuente Mermaid
flowchart TB S[Señales PIT] --> N["Notas -2..+2"] N --> D[Rondas de debate] D --> V[Veredicto] V --> T[Trader: umbral] T --> G[Riesgo: desacuerdo] G --> C[Cartera semanal]
Y = r_sig.iloc[:-1].to_numpy() # (semanas, empresas)
S = {k: v.iloc[:-1].fillna(0).to_numpy() for k, v in SEN.items()}
W, N = Y.shape
def pipeline(semilla, rondas=1, ruido=0.6, umbral=0.75, riesgo=True):
rng = np.random.default_rng(semilla)
def leer(): # cada analista pone su nota
return np.stack([np.where(s != 0, np.clip(np.rint(
s + ruido * rng.standard_normal(s.shape)), -2, 2), 0)
for s in S.values()])
notas = leer()
desacuerdo = notas.std(axis=0)
veredicto = np.zeros((W, N))
for r in range(rondas): # debate alcista / bajista
nr = notas if r == 0 else leer()
veredicto += (np.clip(nr, 0, None).sum(0)
- np.clip(-nr, 0, None).sum(0)) / 4
veredicto /= rondas
compra, venta = veredicto >= umbral, veredicto <= -umbral
if riesgo: # gestor de riesgo conservador
alto = desacuerdo >= 1.25
compra, venta = compra & ~alto, venta & ~alto
nc = np.maximum(compra.sum(1, keepdims=True), 1)
nv = np.maximum(venta.sum(1, keepdims=True), 1)
w = np.where(compra, 0.5 / nc, 0) - np.where(venta, 0.5 / nv, 0)
return w, notas, veredicto, desacuerdo
w, notas, ver, des = pipeline(1)
i, j = FECHAS.get_loc(pd.Timestamp("2024-02-09")), TK.index("SERI")
print("SERI 2024-02-09 | notas:",
dict(zip(S, notas[:, i, j].astype(int).tolist())),
"| veredicto:", ver[i, j], "| desacuerdo:", round(des[i, j], 2),
"| peso:", round(w[i, j], 4))
print("posiciones por semana:", round((w != 0).sum(1).mean(), 1))SERI 2024-02-09 | notas: {'fundamental': 0, 'tecnico': 2, 'noticias': -2, 'sentimiento': 0} | veredicto: 0.0 | desacuerdo: 1.41 | peso: 0.0
posiciones por semana: 24.5El registro de una decisión es el equivalente de los informes estructurados del artículo: cuatro notas, un veredicto, el desacuerdo y el peso final. En Sertis Water, la semana de la lección 6.4.2, el técnico pone +2 (la caída de la semana activa la reversión), el de noticias −2 (la llamada a revisión) y los otros dos 0: el saldo es cero, el desacuerdo alto y el sistema no opera. Es lo que habría que guardar de cada decisión de un sistema real (lección 6.4.5), con el texto de cada informe al lado.
La comparación honesta: baselines y costes
Cualquier sistema de este tipo se compara, como mínimo, con cuatro referencias: el mercado (las 120 acciones a partes iguales), cada señal sola en una cartera larga-corta de quintiles (24 acciones a cada lado), la combinación lineal de las cuatro señales sin ningún agente y la misma regla sin ruido: el veredicto calculado con las señales sin redondear ni leer con error, que es lo que harían los agentes si leyeran perfecto. Los costes son 10 puntos básicos por unidad de rotación (lección 2.4.7), una cifra moderada para acciones líquidas.
COSTE = 0.0010 # 10 pb por unidad de rotación
def metricas(w):
bruto = np.nansum(w * Y, axis=1)
rot = np.abs(np.diff(np.vstack([np.zeros(N), w]), axis=0)).sum(1)
neto = bruto - COSTE * rot
sr = lambda x: x.mean() / x.std() * np.sqrt(52)
return {"bruto_%": 5200 * bruto.mean(),
"neto_%": 5200 * neto.mean(),
"sharpe_bruto": sr(bruto), "sharpe_neto": sr(neto),
"rotacion": rot.mean(),
"t_neto": neto.mean() / neto.std() * np.sqrt(len(neto))}
def quintiles(s, k=24):
rk = pd.DataFrame(s).rank(axis=1, method="first").to_numpy()
return np.where(rk > N - k, 0.5 / k, 0) - np.where(rk <= k,
0.5 / k, 0)
def regla_sin_ruido(umbral=0.75):
st = np.stack([np.clip(s, -2, 2) for s in S.values()])
v = (np.clip(st, 0, None).sum(0) - np.clip(-st, 0, None).sum(0)) / 4
des = np.stack([np.where(s != 0, np.clip(np.rint(s), -2, 2), 0)
for s in S.values()]).std(axis=0)
c = (v >= umbral) & (des < 1.25)
d = (v <= -umbral) & (des < 1.25)
nc = np.maximum(c.sum(1, keepdims=True), 1)
nd = np.maximum(d.sum(1, keepdims=True), 1)
return np.where(c, 0.5 / nc, 0) - np.where(d, 0.5 / nd, 0)
filas = {"mercado (largo)": metricas(np.full((W, N), 1 / N))}
for k, s in S.items():
filas[k] = metricas(quintiles(s))
filas["lineal (4 señales)"] = metricas(quintiles(sum(S.values())))
filas["regla sin ruido"] = metricas(regla_sin_ruido())
filas["multiagente, 1 ronda"] = metricas(pipeline(1)[0])
filas["multiagente, sin riesgo"] = metricas(
pipeline(1, riesgo=False)[0])
pd.DataFrame(filas).T.round(2) bruto_% neto_% sharpe_bruto sharpe_neto rotacion t_neto
mercado (largo) 11.86 11.85 0.58 0.58 0.00 1.55
fundamental 4.56 3.76 1.18 0.97 0.16 2.57
tecnico 3.01 -2.74 0.56 -0.51 1.11 -1.35
noticias 3.70 -3.79 0.93 -0.95 1.44 -2.52
sentimiento 5.81 -1.85 1.57 -0.50 1.47 -1.32
lineal (4 señales) 7.51 2.04 1.52 0.41 1.05 1.10
regla sin ruido 18.94 10.67 1.90 1.07 1.59 2.84
multiagente, 1 ronda 7.89 -0.04 1.24 -0.01 1.52 -0.01
multiagente, sin riesgo 7.56 -0.17 1.24 -0.03 1.49 -0.07La tabla tiene varias lecturas, todas incómodas para el entusiasmo:
- Las señales rápidas no sobreviven a los costes. Técnico, noticias y sentimiento tienen rendimiento bruto positivo (del 3 al 6 %) y neto negativo: rotan entre un 110 y un 147 % por semana. Solo la fundamental, que cambia despacio (rotación del 16 %), conserva rendimiento neto: un 3,8 % anual con un t de 2,57.
- El pipeline multiagente con una ronda queda muy por debajo de la regla sin ruido: un 7,9 % bruto y un neto nulo (−0,04 %), frente al 18,9 % bruto y el 10,7 % neto de la regla, con un Sharpe neto de 1,07. Las dos rotan lo mismo (≈1,5 por semana): la diferencia es información que el ruido de lectura y el redondeo a notas destruyen.
- El gestor de riesgo apenas cambia nada en estos datos: quitar las posiciones con desacuerdo alto no mejora el resultado (la sección del desacuerdo explica por qué).
- Poco es concluyente. Solo la fundamental (t = 2,57) y la regla sin ruido (t = 2,84) pasan de un t neto de 2, y se han probado más de diez configuraciones, con el umbral y el filtro de riesgo elegidos a mano: antes de creer la regla habría que deflactar su Sharpe (lección 2.5.4) y comprobarla en otro periodo. Aquí, además, sabemos que las cuatro señales tienen efectos plantados: una regla que las combine razonablemente tiene que ganar algo. Lo que la tabla demuestra es otra cosa: el pipeline de agentes, con la misma información, gana menos que la regla. El mercado, sin hacer nada, tuvo un Sharpe de 0,58.
Rondas, ejecuciones y llamadas
El artículo hace configurable el número de rondas de debate. La celda mide qué aportan: para 1, 2, 3, 5 y 10 rondas, diez ejecuciones con semillas distintas (diez «tiradas» del mismo sistema) y la regla sin ruido como límite. Cuenta también las llamadas al LLM que haría la arquitectura real con una llamada por agente y turno: cuatro analistas, dos por ronda de debate, el facilitador, el trader, tres agentes de riesgo con una ronda y el gestor del fondo.
decisiones = W * N
res = []
for rondas in (1, 2, 3, 5, 10):
sr = [metricas(pipeline(s, rondas)[0])["sharpe_neto"]
for s in range(1, 11)]
llamadas = 4 + 2 * rondas + 1 + 1 + 3 + 1
res.append({"rondas": rondas, "sharpe_neto_medio": np.mean(sr),
"min": min(sr), "max": max(sr),
"llamadas_decision": llamadas,
"llamadas_total_miles": decisiones * llamadas / 1e3})
print("regla sin ruido: Sharpe neto",
round(metricas(regla_sin_ruido())["sharpe_neto"], 2))
signos = [np.sign(pipeline(s, 1)[0]) for s in range(1, 6)]
print("decisiones que cambian entre ejecuciones (1 ronda):",
round(np.mean([np.mean(signos[0] != x) for x in signos[1:]]), 3))
pd.DataFrame(res).set_index("rondas").round(2)regla sin ruido: Sharpe neto 1.07
decisiones que cambian entre ejecuciones (1 ronda): 0.199
sharpe_neto_medio min max llamadas_decision llamadas_total_miles
rondas
1 0.16 -0.22 0.38 12 525.6
2 0.49 0.25 0.77 14 613.2
3 0.53 0.10 0.83 16 700.8
5 0.68 0.44 1.03 20 876.0
10 0.65 0.45 0.88 30 1314.0La figura de la cabecera lo dibuja. Con una ronda, el Sharpe neto medio de diez ejecuciones es 0,16 y va de −0,22 a 0,38: el mismo sistema, con los mismos datos, puede salir perdedor o modesto según la tirada, y un 20 % de sus decisiones cambia de una ejecución a otra. Cada ronda adicional vuelve a leer la evidencia y el promedio sube hacia la regla sin ruido (1,07): 0,49 con dos rondas, 0,68 con cinco y 0,65 con diez (la diferencia entre cinco y diez es ruido de las semillas). Ni con diez rondas llega: el redondeo a notas enteras sigue perdiendo información aunque se promedie. El debate no descubre nada: promedia lecturas ruidosas de la misma información, como un ensemble (lección 5.10.3), y su límite es una regla que no necesita ningún LLM.
El precio crece en línea recta: 12 llamadas por decisión con una ronda, 30 con diez, y entre 525 600 y 1,3 millones de llamadas para un backtest semanal de 120 acciones en siete años. Es la razón que da el artículo para limitar su evaluación a tres meses, y la razón por la que un backtest largo y repetido de una arquitectura así casi nunca se publica.
¿Avisa el desacuerdo de los errores?
Una idea atractiva: si los analistas no se ponen de acuerdo, la decisión es más arriesgada y el gestor de riesgo debe frenar. La celda lo comprueba con el pipeline sin gestor de riesgo: para cada posición abierta, si el signo coincide con el del rendimiento de la semana siguiente menos la media de las 120 (acierto), agrupado por la desviación de las cuatro notas.
w, notas, ver, des = pipeline(1, riesgo=False)
exceso = Y - np.nanmean(Y, axis=1, keepdims=True)
abierta = w != 0
acierto = (np.sign(w) == np.sign(exceso))[abierta]
nivel = pd.cut(des[abierta], [-0.01, 0.6, 1.1, 9],
labels=["bajo", "medio", "alto"])
pd.Series(acierto).groupby(nivel, observed=True).agg(
acierto="mean", posiciones="size").round(3) acierto posiciones
bajo 0.505 1001
medio 0.517 7787
alto 0.520 865El acierto está entre el 50,5 y el 52 % en los tres niveles, y es incluso algo mayor con desacuerdo alto: el desacuerdo no avisa de nada. Tiene sentido con estos datos: las cuatro señales vienen de efectos plantados independientes, y que la de noticias diga comprar mientras la técnica dice vender es lo normal, no un síntoma de error. En datos reales las señales están más correlacionadas y el desacuerdo podría significar otra cosa, pero el principio vale: una regla de riesgo basada en el desacuerdo se mide igual que cualquier otra, con su coste y su efecto, antes de ponerla en producción (lección 4.2).
Qué aprovechar de estas arquitecturas
Nada de lo anterior hace inútil la idea de repartir el trabajo. Lo que la lección separa es dónde aporta un LLM y dónde no:
| Pieza | Aporta un LLM | Mejor sin LLM |
|---|---|---|
| Extraer datos de texto (guidance, KPIs, tono) | Sí, validado contra una verdad (lección 6.2.3) | — |
| Resumir la evidencia para una persona | Sí, con citas comprobadas (lección 6.4.2) | — |
| Combinar señales numéricas | — | Una regla o un modelo estadístico, medido fuera de muestra |
| Dimensionar posiciones y límites de riesgo | — | Un motor de riesgo determinista (lección 6.4.5) |
| Decidir si se opera | — | La regla, con aprobación humana en los cambios |
Los informes escritos, el debate y la justificación en lenguaje natural tienen valor para explicar una decisión a una persona, que es lo que el artículo destaca como ventaja frente a los modelos de deep learning. La decisión en sí conviene que la tome algo reproducible.
Errores típicos
- Creer un Sharpe de tres meses. Con 60 sesiones, el error típico del Sharpe anualizado supera los 2 puntos.
- Olvidar los costes. Las señales de noticias y redes rotan más del 100 % semanal; sin costes parecen buenas.
- Una sola ejecución. El mismo pipeline da Sharpe netos de −0,22 a 0,38 según la semilla.
- Comparar solo con reglas técnicas de una acción. La referencia justa es la misma información combinada sin LLM.
- Ignorar la memoria del modelo. Un backtest sobre fechas anteriores al corte del modelo mide lo que recuerda (lección 6.2.5).
- Pagar rondas de debate por un promedio. Si el debate converge a una regla, la regla es más barata, más rápida y no cambia entre ejecuciones.
Ejercicios
- Repite la comparación con 0, 10 y 20 puntos básicos por unidad de rotación para la combinación lineal, la regla sin ruido y el pipeline con una ronda. ¿A partir de qué coste deja de tener sentido cada uno?
Solución
filas = {}
for c in (0.0, 0.0010, 0.0020):
COSTE = c
filas[f"{c * 1e4:.0f} pb"] = {
"lineal": metricas(quintiles(sum(S.values())))["neto_%"],
"regla": metricas(regla_sin_ruido())["neto_%"],
"multiagente": metricas(pipeline(1)[0])["neto_%"]}
COSTE = 0.0010
pd.DataFrame(filas).round(2) 0 pb 10 pb 20 pb
lineal 7.51 2.04 -3.42
regla 18.94 10.67 2.40
multiagente 7.89 -0.04 -7.96Las tres rotan entre un 100 y un 160 % por semana, así que cada 10 puntos básicos de coste les quitan entre un 5 y un 8 % anual. Sin costes todas ganan; con 10 puntos básicos el pipeline de una ronda queda en cero, la combinación lineal conserva un 2 % y la regla sin ruido un 10,7 %; con 20, solo la regla sigue en positivo (2,4 %), y el pipeline pierde casi un 8 %. Una arquitectura que decide cada semana sobre 120 acciones necesita, antes que agentes, una regla de rotación (umbrales de entrada y salida, rebalanceo menos frecuente).
- Quita el analista de sentimiento (el de IC más alto) del pipeline y de la regla sin ruido. ¿Qué pasa con el rendimiento bruto y con el neto? ¿Por qué?
Solución
S_todo = S
S = {k: v for k, v in S_todo.items() if k != "sentimiento"}
r = {"regla sin ruido": metricas(regla_sin_ruido()),
"multiagente, 1 ronda": metricas(pipeline(1)[0])}
S = S_todo
pd.DataFrame(r).T[["bruto_%", "neto_%", "rotacion"]].round(2) bruto_% neto_% rotacion
regla sin ruido 13.74 5.90 1.51
multiagente, 1 ronda 7.39 -0.34 1.49Sin el sentimiento, las dos versiones pierden rendimiento bruto (la regla pasa del 18,9 al 13,7 % y el pipeline del 7,9 al 7,4 %): era la señal con más IC. La rotación apenas baja (de 1,59 a 1,51 y de 1,52 a 1,49), porque las otras señales rápidas siguen ahí, así que el neto también cae: del 10,7 al 5,9 % en la regla y del 0,0 al −0,3 % en el pipeline. Aquí la señal de sentimiento pagaba su coste dentro de la combinación, aunque sola perdiera dinero: la pregunta útil con cada analista es cuánto aporta neto de lo que cuesta al conjunto, no cuánto IC tiene ni cuánto gana por separado.
- Sin código: un proveedor te ofrece un sistema multiagente «al estilo TradingAgents» con «un Sharpe de 3,1 en backtest de 2023 a 2025 sobre el S&P 500» y te enseña un ejemplo de su debate entre agentes, muy convincente. ¿Qué le pedirías antes de seguir hablando?
Solución
El número de ejecuciones del backtest y la dispersión del resultado entre ellas (con una sola, el Sharpe es una tirada); los costes supuestos y la rotación; qué modelos usa y su fecha de corte, porque un backtest de 2023 a 2025 con modelos entrenados con textos de esos años mide memoria (lección 6.2.5); cuántas configuraciones se probaron antes de elegir esta (Deflated Sharpe, lección 2.5.4); la comparación con la misma información combinada sin LLM; el número de llamadas por decisión y su coste; y un periodo posterior al corte de todos los modelos, en vivo o en paper trading, como hace AlphaCrafter. Un debate convincente no es una prueba: es texto generado por el mismo sistema que se evalúa.
Respuesta plegada en cada pregunta
Autoevaluación
El artículo de TradingAgents publica, con datos de enero a marzo de 2024, un rendimiento acumulado del 26,62 % y un Sharpe de 8,21 en AAPL. ¿Qué lectura es correcta?
- AQue la arquitectura multiagente bate de forma fiable al mercado
- BQue con un Sharpe de 8 la estrategia está libre de riesgo
- CQue es un resultado de tres meses, en tres acciones mostradas, sin costes de transacción declarados, con modelos que pudieron leer textos de ese periodo; los propios autores dicen que el Sharpe supera el rango que esperaban
- DQue los baselines elegidos (MACD, SMA…) eran estrategias de referencia profesionales
Ver respuesta
Respuesta C. Que es un resultado de tres meses, en tres acciones mostradas, sin costes de transacción declarados, con modelos que pudieron leer textos de ese periodo; los propios autores dicen que el Sharpe supera el rango que esperaban
La nota al pie del artículo explica que evaluaron solo tres meses por el coste de 11 llamadas al LLM y más de 20 a herramientas por predicción, y que el Sharpe más alto «exceeds our expected empirical range». Un Sharpe sobre 60 sesiones tiene un error típico enorme, y sin costes ni control del sesgo de anticipación no hay base para generalizar.
En la lección, el pipeline multiagente con una sola ronda de debate da un Sharpe neto medio de 0,16 en diez ejecuciones, con 5 o 10 rondas sube a 0,65-0,68 y la regla determinista equivalente, sin ningún LLM, da 1,07. ¿Qué explica el patrón?
- AQue el debate genera información nueva en cada ronda
- BQue cada ronda vuelve a leer la misma evidencia con un ruido nuevo: promediar lecturas reduce el ruido y el veredicto converge a una función fija de las señales, que se puede escribir sin LLM
- CQue con más rondas el modelo aprende de sus errores
- DQue los costes bajan con el número de rondas
Ver respuesta
Respuesta B. Que cada ronda vuelve a leer la misma evidencia con un ruido nuevo: promediar lecturas reduce el ruido y el veredicto converge a una función fija de las señales, que se puede escribir sin LLM
Los agentes simulados no inventan señal: leen las cuatro señales con un error y las redondean a una nota. Repetir la lectura y promediar es un ensemble que se acerca al valor esperado de las notas. El límite es una regla determinista, que cuesta cero llamadas y no varía entre ejecuciones.
Entre dos ejecuciones del pipeline con una ronda, un 20 % de las decisiones (comprar, vender o no operar) cambian. ¿Qué implica para evaluar un agente de este tipo?
- AQue una sola ejecución del backtest no basta: hay que repetirla con varias semillas o muestras y dar la distribución del resultado
- BQue basta con fijar la temperatura a cero para que deje de pasar
- CQue el 20 % de las decisiones son errores
- DNada: el Sharpe medio es lo único que importa
Ver respuesta
Respuesta A. Que una sola ejecución del backtest no basta: hay que repetirla con varias semillas o muestras y dar la distribución del resultado
Con una ronda, el Sharpe neto de diez ejecuciones va de −0,22 a 0,38: el resultado de una ejecución es una muestra de una distribución ancha. La lección 6.4.4 muestra que con LLM reales, incluso con decodificación determinista, las acciones también cambian entre ejecuciones.
La señal de redes sociales tiene el IC semanal más alto de las cuatro (0,018, t = 3,8), pero su cartera larga-corta pierde un 1,9 % anual neto. ¿Por qué?
- APorque el IC está mal calculado
- BPorque las redes sociales no tienen efecto en estos datos
- CPorque la cartera es larga solo
- DPorque la señal cambia casi por completo cada semana: rota un 147 % semanal y 10 puntos básicos por unidad de rotación se comen todo el rendimiento bruto del 5,8 %
Ver respuesta
Respuesta D. Porque la señal cambia casi por completo cada semana: rota un 147 % semanal y 10 puntos básicos por unidad de rotación se comen todo el rendimiento bruto del 5,8 %
El sentimiento minorista del generador es persistente solo unos días (AR(1) diario con ρ = 0,7) y las carteras de quintiles se rehacen casi enteras cada semana. Una señal con IC alto y vida corta vale poco si se opera con costes (lección 2.4.7).
¿Predice el desacuerdo entre los cuatro analistas que la decisión vaya a fallar?
- ASí: con desacuerdo alto el acierto baja al 30 %
- BNo en estos datos: el acierto está entre el 50,5 y el 52 % en los tres niveles de desacuerdo, porque las cuatro fuentes miden efectos independientes y discrepar es lo normal
- CSí, y por eso el gestor de riesgo debería operar solo cuando hay desacuerdo
- DNo se puede medir sin un LLM real
Ver respuesta
Respuesta B. No en estos datos: el acierto está entre el 50,5 y el 52 % en los tres niveles de desacuerdo, porque las cuatro fuentes miden efectos independientes y discrepar es lo normal
Las cuatro señales vienen de efectos plantados distintos (valor y sorpresa, momentum y reversión, deriva de noticias, sentimiento minorista). Que una diga comprar y otra vender no indica que alguna se equivoque. Filtrar por desacuerdo, como hace el gestor de riesgo de la lección, reduce posiciones pero no mejora el acierto.
Ideas clave
- TradingAgents organiza una mesa simulada: cuatro analistas (fundamental, sentimiento, noticias, técnico), investigadores alcista y bajista que debaten n rondas, un trader, un equipo de riesgo con tres perfiles y un gestor que aprueba. Su evaluación original cubre tres meses de 2024 sin costes declarados.
- Un agente analista solo puede transmitir la señal que hay en sus datos: en el curso, las cuatro señales tienen IC semanales de 0,010 a 0,018 frente a efectos plantados conocidos.
- El debate entre agentes que leen con ruido funciona como un promedio: más rondas acercan el veredicto a una regla determinista que se escribe sin LLM, mientras las llamadas crecen con 2 por ronda y empresa.
- Las decisiones cambian entre ejecuciones (un 20 % con una ronda) y el Sharpe neto de diez ejecuciones va de −0,22 a 0,38: un backtest de una sola ejecución no describe al sistema.
- Con 10 puntos básicos por unidad de rotación, las señales rápidas pierden dinero; la fundamental sola y la regla sin LLM son las únicas con un t neto por encima de 2, y tras probar más de diez configuraciones, Deflated Sharpe antes de creer nada (lección 2.5.4).
Recursos
Documentación
Libros
- Xiao, Y., Sun, E., Luo, D. y Wang, W.: TradingAgents: Multi-Agents LLM Financial Trading Framework (arXiv:2412.20138, v7)
- Yuan, Y. et al. (2026): AlphaCrafter: Harnessing Multi-Agent Workflows for Cross-Sectional Quantitative Trading (arXiv:2605.05580, v2)
- Du, Y. et al. (2023): Improving Factuality and Reasoning in Language Models through Multiagent Debate (arXiv:2305.14325)