---
title: "6.4.3 Arquitecturas multiagente al estilo TradingAgents"
description: "Analistas, debate alcista y bajista, trader y gestor de riesgo: TradingAgents leído en su fuente y replicado con agentes simulados, costes y baselines."
url: "https://pythonparatrading.com/curso-python-trading/nivel-6/arquitecturas-multiagente-tipo-tradingagents/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-6/arquitecturas-multiagente-tipo-tradingagents.png"
---

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

- Curso: [Nivel 6: IA generativa, LLMs, agentes, big data y datos alternativos](https://pythonparatrading.com/curso-python-trading/nivel-6/)
- Módulo: 6.4 · Agentes de IA para trading
- Duración: 75 min
- Requisitos: [6.4.2 · Marcos de agentes y MCP](https://pythonparatrading.com/curso-python-trading/nivel-6/langgraph-llamaindex-sdks-y-mcp/), [6.4.1 · Agentes con herramientas: tool calling, ReAct y planificación](https://pythonparatrading.com/curso-python-trading/nivel-6/agentes-con-herramientas-react-y-planificacion/), [6.1.3 · Event studies con señales de texto, alineación temporal y topic modeling](https://pythonparatrading.com/curso-python-trading/nivel-6/event-studies-con-texto-y-topic-modeling/), [2.4.7 · Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos](https://pythonparatrading.com/curso-python-trading/nivel-2/modelado-de-costes/), [2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)](https://pythonparatrading.com/curso-python-trading/nivel-2/deflated-sharpe-ratio-y-pbo/)

## Al terminar esta lección

- Describir la arquitectura de TradingAgents (equipo de analistas, investigadores alcista y bajista, trader, equipo de riesgo y gestor) y su evaluación original, con sus cifras y sus límites.
- Construir cuatro analistas point-in-time sobre los datos del curso y medir su IC frente a los efectos plantados.
- Implementar un pipeline multiagente determinista con analistas que leen con ruido, rondas de debate, trader y gestor de riesgo, y compararlo con baselines simples después de costes.
- Medir cuánto cambian las decisiones entre ejecuciones, cuánto aportan las rondas de debate y cuántas llamadas a un LLM costaría la arquitectura.
- Comprobar si el desacuerdo entre analistas anticipa los errores.

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

1. **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.
2. **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.
3. **Trader**: convierte el análisis en una decisión (comprar, vender o mantener) con su justificación.
4. **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.
5. **Gestor del fondo**: revisa esa discusión, decide los ajustes y aprueba la operación.

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

```python
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")
```
```output
366 semanas, 2019-01-04 a 2025-12-31 | 120 empresas
```

```python
piv = 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)
```
```output
                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.520
```

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

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

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

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

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

*Fig. — Rendimiento anual bruto y neto de costes: señales, combinación, regla sin LLM y pipeline multiagente (2019-2025).* Barras horizontales con el rendimiento anual neto de 10 puntos básicos por unidad de rotación y una marca con el bruto: mercado 11,8 % neto; fundamental 3,8 %; técnico −2,7 %; noticias −3,8 %; sentimiento −1,9 %; combinación lineal 2,0 %; regla sin ruido 10,7 % (18,9 % bruto); pipeline multiagente de una ronda, destacado, −0,04 % neto con 7,9 % bruto.

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

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

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

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

El debate entre agentes sí tiene apoyo en otras tareas: Du et al. (2023, arXiv:2305.14325) muestran que varias
instancias de un modelo que proponen y discuten sus respuestas durante varias rondas mejoran en razonamiento matemático
y estratégico y reducen las respuestas falsas. Esa mejora
viene de corregir errores de razonamiento verificables. En una predicción de rendimientos, donde la información está
en los datos y el error dominante es el ruido del mercado, el mecanismo útil del debate se reduce a promediar.

</aside>

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

```python
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)
```
```output
       acierto  posiciones
bajo     0.505        1001
medio    0.517        7787
alto     0.520         865
```

El 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

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

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

Las 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).

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

```python
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)
```
```output
                      bruto_%  neto_%  rotacion
regla sin ruido         13.74    5.90      1.51
multiagente, 1 ronda     7.39   -0.34      1.49
```

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

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

## Autoevaluación

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

   - A. Que la arquitectura multiagente bate de forma fiable al mercado
   - B. Que con un Sharpe de 8 la estrategia está libre de riesgo
   - 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
   - D. Que los baselines elegidos (MACD, SMA…) eran estrategias de referencia profesionales

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

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

   - A. Que el debate genera información nueva en cada ronda
   - 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
   - C. Que con más rondas el modelo aprende de sus errores
   - D. Que los costes bajan con el número de rondas

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

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

   - 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
   - B. Que basta con fijar la temperatura a cero para que deje de pasar
   - C. Que el 20 % de las decisiones son errores
   - D. Nada: el Sharpe medio es lo único que importa

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

4. 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é?

   - A. Porque el IC está mal calculado
   - B. Porque las redes sociales no tienen efecto en estos datos
   - C. Porque la cartera es larga solo
   - 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 %

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

5. ¿Predice el desacuerdo entre los cuatro analistas que la decisión vaya a fallar?

   - A. Sí: con desacuerdo alto el acierto baja al 30 %
   - 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
   - C. Sí, y por eso el gestor de riesgo debería operar solo cuando hay desacuerdo
   - D. No se puede medir sin un LLM real

   *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

- [TauricResearch/TradingAgents: código del marco (Apache-2.0; README consultado el 2026-10-23)](https://github.com/TauricResearch/TradingAgents) · github.com

### Libros

- [Xiao, Y., Sun, E., Luo, D. y Wang, W.: TradingAgents: Multi-Agents LLM Financial Trading Framework (arXiv:2412.20138, v7)](https://arxiv.org/abs/2412.20138) · arxiv.org
- [Yuan, Y. et al. (2026): AlphaCrafter: Harnessing Multi-Agent Workflows for Cross-Sectional Quantitative Trading (arXiv:2605.05580, v2)](https://arxiv.org/abs/2605.05580) · arxiv.org
- [Du, Y. et al. (2023): Improving Factuality and Reasoning in Language Models through Multiagent Debate (arXiv:2305.14325)](https://arxiv.org/abs/2305.14325) · arxiv.org

### Datos

- [acciones6_precios.parquet: precios de las 120 empresas del nivel 6 (datos sintéticos)](https://pythonparatrading.com/curso/datos/nivel-6/acciones6_precios.parquet) · Descarga · acciones6_precios.parquet
- [social6_posts.parquet: publicaciones en redes (datos sintéticos)](https://pythonparatrading.com/curso/datos/nivel-6/social6_posts.parquet) · Descarga · social6_posts.parquet

Lección anterior: [6.4.2 · Marcos de agentes y MCP](https://pythonparatrading.com/curso-python-trading/nivel-6/langgraph-llamaindex-sdks-y-mcp/)

Lección siguiente: [6.4.4 · Evidencia y límites de los agentes](https://pythonparatrading.com/curso-python-trading/nivel-6/evidencia-y-limites-de-los-agentes-de-trading/)
