// CAPSTONE · MÓDULO 12/12

Proyecto capstone: un hedge fund multi-agente gobernado

Integración completa: research, riesgo, cartera y ejecución con firma humana en un sistema único.

Lectura ~36 minNivel intermedio-avanzadoEdición jul 2026Read in English


Los once módulos anteriores construyeron las piezas por separado: datos point-in-time (M4), RAG consciente de la estructura financiera (M5), agentes y grafos (M3), medición de riesgo (M7), construcción de carteras con vistas Black-Litterman (M8), señales y backtesting (M9), gobernanza (M10) e ingeniería de producción (M11). Este módulo las integra en un único entregable: un hedge fund multi-agente gobernado que, cada mañana, analiza una watchlist, debate las tesis, las pasa por un filtro de riesgo cuantitativo, traduce el resultado a pesos con un optimizador determinista y produce un informe auditable — sin que ninguna orden se envíe sin firma humana. La regla de oro del curso gobierna cada arista del grafo: el LLM razona, la matemática decide, el humano veta. El sistema se entrega contra Alpaca paper trading, porque el checklist del Módulo 9 exige meses de ejecución simulada con criterio de paridad pre-registrado antes de cualquier capital real. (LangChain)

12.1 Arquitectura del sistema completo

12.1.1 Diagrama integral y la matriz FSM/grafo/agente aplicada

El pipeline completo tiene seis subsistemas encadenados. (1) Research swarm: cuatro analistas —fundamental, técnico, sentimiento y macro— ejecutan en paralelo sobre el estado compartido, cada uno como un create_agent con sus propias tools (el patrón fan-out con reducer del Módulo 3, y el organigrama de TradingAgents que se estudió en 3.3.2). (langchain.js) (2) Debate bull/bear/judge: grafo con ciclos acotados (max_debate_rounds = 2) y juez con routing explícito; el LLM genera argumentos, el grafo decide quién habla después. (3) Risk gate: nodo determinista que calcula el CVaR del peor 1 % del P&L propuesto con la tool del Módulo 7 —la señal de gating de FinCon— y veta, recorta o escala al humano. (Github) (4) Rebalanceo Black-Litterman: las tesis supervivientes se convierten en vistas (P,q,Ω)(P, \mathbf{q}, \boldsymbol{\Omega}) con Ω\boldsymbol{\Omega} deliberadamente alta, como se vio en el Módulo 8, y el optimizador PyPortfolioOpt calcula los pesos objetivo. (5) Ejecución paper: el delta de pesos se traduce a órdenes que atraviesan el RiskGateway y un interrupt HITL antes de Alpaca paper. (6) Informe de inversión: un agente redactor compone el documento final con grounding verificable: toda cifra proviene de una tool o de un documento citado. Dos infraestructuras transversales lo envuelven todo: el checkpointer Postgres como audit log consultable con SQL, (🦜️🔗 LangChain) y LangSmith trazando cada llamada, cada tool y cada token, de principio a fin — la única defensa ante el vacío regulatorio de la IA agéntica que documentó el Módulo 10. (United Fintech) (copia archivada)

Grafo del capstone, de la watchlist al informe: analistas, debate, risk gate, rebalanceo y órdenes con aprobación humana, con PostgresSaver y LangSmith trazando todo.START watchlist diaria → fan-out paralelo reducer: concat reports; fan-out paralelo reducer: concat reports → analista_fundamental create_agent + XBRL/filings; fan-out paralelo reducer: concat reports → analista_técnico create_agent + OHLCV Massive; fan-out paralelo reducer: concat reports → analista_sentimiento create_agent + noticias PIT; fan-out paralelo reducer: concat reports → analista_macro create_agent + series macro; analista_fundamental create_agent + XBRL/filings → debate bull/bear grafo con ciclos acotados; analista_técnico create_agent + OHLCV Massive → debate bull/bear grafo con ciclos acotados; analista_sentimiento create_agent + noticias PIT → debate bull/bear grafo con ciclos acotados; analista_macro create_agent + series macro → debate bull/bear grafo con ciclos acotados; debate bull/bear grafo con ciclos acotados → juez / research manager veredicto estructurado; juez / research manager veredicto estructurado → risk_gate subgrafo: tool CVaR determinista + interrupt si brecha; risk_gate subgrafo: tool CVaR determinista + interrupt si brecha → log de veto memoria de tesis → store (veto); risk_gate subgrafo: tool CVaR determinista + interrupt si brecha → rebalanceo Black-Litterman vistas con Omega alta PyPortfolioOpt (aprobado/recortado); rebalanceo Black-Litterman vistas con Omega alta PyPortfolioOpt → órdenes delta RiskGateway determinista; órdenes delta RiskGateway determinista → interrupt HITL aprobación del trader; interrupt HITL aprobación del trader → Alpaca paper trading client_order_id idempotente (approve); interrupt HITL aprobación del trader → log de veto memoria de tesis → store (reject); Alpaca paper trading client_order_id idempotente → informe de inversión grounding verificable; log de veto memoria de tesis → store → informe de inversión grounding verificable; informe de inversión grounding verificable → END; debate bull/bear grafo con ciclos acotados → PostgresSaver audit log SQL (checkpoint por pas); risk_gate subgrafo: tool CVaR determinista + interrupt si brecha → PostgresSaver audit log SQL; interrupt HITL aprobación del trader → PostgresSaver audit log SQL; fan-out paralelo reducer: concat reports → LangSmith end-to-end (trazas OTel); rebalanceo Black-Litterman vistas con Omega alta PyPortfolioOpt → LangSmith end-to-end; informe de inversión grounding verificable → LangSmith end-to-endvetoaprobado/recortadoapproverejectcheckpoint por pastrazas OTelSTARTwatchlist diariafan-out paraleloreducer: concat reportsanalista_fundamentalcreate_agent + XBRL/filingsanalista_técnicocreate_agent + OHLCV Massiveanalista_sentimientocreate_agent + noticias PITanalista_macrocreate_agent + series macrodebate bull/beargrafo con ciclos acotadosjuez / research managerveredicto estructuradorisk_gatesubgrafo: tool CVaR determinista+ interrupt si brechalog de vetomemoria de tesis → storerebalanceo Black-Littermanvistas con Omega altaPyPortfolioOptórdenes deltaRiskGateway deterministainterrupt HITLaprobación del traderAlpaca paper tradingclient_order_id idempotenteinforme de inversióngrounding verificableENDPostgresSaveraudit log SQLLangSmithend-to-end
Arquitectura completa del capstone. Cuatro analistas en paralelo alimentan debate, juez y risk gate; Black-Litterman fija los pesos y cada orden pasa RiskGateway y firma humana antes de Alpaca paper.

La decisión arquitectónica de cada subsistema no es improvisada: aplica la matriz FSM → grafo → agente → multi-agente del Módulo 3, con la condición de Anthropic — workflows cuando el proceso es repetible y mandan auditabilidad y SLAs; agentes cuando el número de pasos se desconoce y hay feedback verificable por turno. (Grepture) La Tabla 12.1 asigna a cada subsistema su lugar en el espectro.

Tabla 12.1 — Matriz FSM/grafo/agente aplicada a los subsistemas del capstone

SubsistemaPosición en el espectroJustificación
Research swarm (4 analistas)Agente autónomo (create_agent) en fan-out paraleloEl número de tool calls por análisis no se conoce ex ante (¿cuántos filings leer?); feedback verificable por turno; el paralelismo con reducer hace el coste predecible en llamadas
Debate bull/bear/judgeGrafo con ciclos acotadosEl contenido es libre pero la estructura es fija: rondas contadas, juez como nodo, caminos enumerables y testeables con estado sintético
Risk gate (CVaR)FSM pura + nodo deterministaDecisión binaria/multivía sobre una cifra calculada en código; cero autonomía: el LLM solo narra la alerta, nunca calcula el CVaR (Github)
Rebalanceo BLWorkflow determinista (sin LLM en el camino crítico)Optimización matemática con restricciones; el LLM solo generó las vistas aguas arriba, con Ω\boldsymbol{\Omega} alta (ProfitLogic)
Ejecución (RiskGateway + orden)FSM determinista con interrupt humanoEspíritu 15c3-5: controles pre-trade indelegables en código; el humano firma; latencia de milisegundos excluye al LLM (skywork.ai)
Informe de inversiónAgente acotado con salida estructuradaSíntesis multi-documento con esquema cerrado y verificación numérica post-hoc; un LLM-as-judge sobre cifras sería circular (🦜️🔗 LangChain)

12.2 Implementación guiada

12.2.1 Tools deterministas, subgrafos, interrupt HITL y memoria de tesis

Capa de tools deterministas. Toda cifra del sistema nace en código con validación Pydantic en la frontera: esquemas cerrados, rangos rechazados antes del cómputo y redondeo controlado en la salida, para que el LLM reciba la cifra ya fijada (el patrón del Módulo 7). Tres tools cubren el pipeline: precios OHLCV contra Massive (ex-Polygon.io, renombrado el 30-oct-2025; los endpoints api.polygon.io siguen operativos en paralelo), (Quant Memo) el calculador VaR/CVaR del Módulo 7, y el optimizador Black-Litterman del Módulo 8.

python
from pydantic import BaseModel, Field, field_validator
from langchain_core.tools import tool
import numpy as np

class PriceQuery(BaseModel):
    ticker: str = Field(pattern=r"^[A-Z]{1,6}$")
    start: str = Field(pattern=r"^\d{4}-\d{2}-\d{2}$")
    end: str = Field(pattern=r"^\d{4}-\d{2}-\d{2}$")

    @field_validator("end")
    @classmethod
    def end_after_start(cls, v, info):
        if v <= info.data["start"]:
            raise ValueError("end debe ser posterior a start (anti look-ahead)")
        return v

@tool(args_schema=PriceQuery)
def get_ohlcv(ticker: str, start: str, end: str) -> dict:
    """OHLCV diario point-in-time desde Massive (ex-Polygon.io)."""
    bars = massive_client.get_aggs(ticker, 1, "day", start, end)  # ajustados
    return {"ticker": ticker, "n_bars": len(bars),
            "closes": [round(b.close, 4) for b in bars],
            "as_of": end}                      # linaje temporal explícito

@tool
def compute_cvar_gating(daily_pnls: list[float], alpha: float = 0.01) -> dict:
    """CVaR del peor alpha del P&L diario (gating estilo FinCon). Determinista.
    Signo negativo = pérdida, comparable directamente con CVAR_BUDGET."""
    losses = np.sort(-np.asarray(daily_pnls, dtype=float))
    k = max(1, int(np.ceil(alpha * len(losses))))
    return {"cvar": round(float(-losses[-k:].mean()), 6),   # p.ej. -0.0184
            "var": round(float(-losses[-k]), 6), "k_tail": k}

La tool de optimización encapsula el pipeline del Módulo 8 — covarianza Ledoit-Wolf, prior de equilibrio, posterior BL con Ω\boldsymbol{\Omega} inflada por el factor prudencial y restricciones que actúan como shrinkage de Jagannathan-Ma — de modo que el grafo nunca manipula matrices directamente:

python
from pypfopt.black_litterman import BlackLittermanModel
from pypfopt import EfficientFrontier

class BLInput(BaseModel):
    tickers: list[str]
    views: dict[str, float]          # q: retornos anuales de las tesis aprobadas
    view_var: dict[str, float]       # Omega diagonal: dispersión de consultas x factor prudencial
    tau: float = 0.025
    omega_inflation: float = 3.0     # Módulo 8: la confianza lingüística no es tradable [(ProfitLogic)](https://www.davidhbailey.com/dhbpapers/deflated-sharpe.pdf) 

@tool(args_schema=BLInput)
def optimize_bl(tickers, views, view_var, tau=0.025, omega_inflation=3.0) -> dict:
    """Pesos objetivo BL long-only con cota por activo. Determinista."""
    S_lw = ledoit_wolf_cov(tickers)                    # nunca covarianza muestral cruda
    omega = {k: v * omega_inflation for k, v in view_var.items()}
    bl = BlackLittermanModel(S_lw, pi="market_cap",
                             absolute_views=views, omega=omega, tau=tau)
    ef = EfficientFrontier(bl.bl_returns(), bl.bl_cov())
    ef.add_constraint(lambda w: w >= 0)
    ef.add_constraint(lambda w: w <= 0.10)             # cota = regularización
    return {"weights": {k: round(v, 4) for k, v in
                        ef.max_sharpe(risk_free_rate=0.03).items() if v > 1e-4}}

Supervisor con subgrafos. El grafo padre es un StateGraph cuyos nodos son subgrafos compilados: los cuatro analistas son agentes create_agent (recordemos: devuelven un CompiledStateGraph anidable), (Github) el debate es el StateGraph de ciclos acotados del Módulo 3, y el risk gate es un subgrafo propio. El estado compartido usa reducers explícitos — la lección del "bug del reducer" del Módulo 3 — y el checkpointer PostgresSaver dobla como audit log consultable: (🦜️🔗 LangChain)

python
from typing import Annotated, TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.types import Command

class CapstoneState(TypedDict):
    tickers: list[str]
    reports: Annotated[list[dict], lambda a, b: a + b]   # fan-out: se acumulan
    verdicts: Annotated[list[dict], lambda a, b: a + b]  # tesis del juez
    risk_decisions: list[dict]                            # por tesis: pass/cut/veto
    target_weights: dict
    orders: list[dict]
    report_md: str

checkpointer = PostgresSaver.from_conn_string("postgresql://langgraph:****@db.internal/lg")
checkpointer.setup()   # una vez por despliegue; retención programada (M3)

def route_after_research(state: CapstoneState) -> Command[Literal["debate"]]:
    return Command(update={}, goto="debate")   # routing explícito, nada implícito

capstone = (
    StateGraph(CapstoneState)
    .add_node("analyst_fund", fundamental_agent)    # CompiledStateGraph anidado
    .add_node("analyst_tech", technical_agent)
    .add_node("analyst_sent", sentiment_agent)
    .add_node("analyst_macro", macro_agent)
    .add_node("debate", debate_subgraph)            # bull/bear/judge (M3)
    .add_node("risk_gate", risk_gate_subgraph)      # subgrafo con interrupt
    .add_node("bl_rebalance", bl_node)              # llama a optimize_bl
    .add_node("execution", execution_subgraph)      # RiskGateway + HITL + Alpaca
    .add_node("report", report_agent)               # informe con grounding
    .add_edge(START, "analyst_fund").add_edge(START, "analyst_tech")
    .add_edge(START, "analyst_sent").add_edge(START, "analyst_macro")
    .add_edge("analyst_fund", "debate").add_edge("analyst_tech", "debate")
    .add_edge("analyst_sent", "debate").add_edge("analyst_macro", "debate")
    .add_edge("debate", "risk_gate").add_edge("risk_gate", "bl_rebalance")
    .add_edge("bl_rebalance", "execution").add_edge("execution", "report")
    .add_edge("report", END)
    .compile(checkpointer=checkpointer, store=thesis_store)
)

Subgrafo del risk gate con interrupt. El risk gate es FSM pura: calcula el CVaR propuesto con la tool, lo compara contra el presupuesto de cola del mandato y toma una de tres rutas — aprobar, recortar al 50 % o escalar al humano con interrupt(). El LLM solo entra para redactar la alerta; la cifra que la gobierna es de la tool, siguiendo el patrón FinCon: la métrica de cola decide, el modelo de lenguaje narra. (Github)

python
from langgraph.types import interrupt

CVAR_BUDGET = -0.02        # peor 1% de dias: perdida diaria max. tolerable 2%

def risk_gate_node(state: CapstoneState) -> dict:
    decisions = []
    for v in state["verdicts"]:
        pnls = simulate_daily_pnl(v["ticker"], v["side"], v["conviction"])  # determinista
        risk = compute_cvar_gating.invoke({"daily_pnls": pnls})
        if risk["cvar"] > CVAR_BUDGET:                       # dentro de presupuesto
            decisions.append({"ticker": v["ticker"], "action": "pass", **risk})
        elif risk["cvar"] > 1.5 * CVAR_BUDGET:               # brecha moderada: recorte
            decisions.append({"ticker": v["ticker"], "action": "cut_50", **risk})
        else:                                                 # brecha severa: humano
            alert = alert_llm.invoke(f"Redacta alerta: {risk}, tesis: {v}")
            decision = interrupt({"alert": alert, "risk": risk, "thesis": v})
            decisions.append({"ticker": v["ticker"],
                              "action": decision["action"], "by": "human", **risk})
    return {"risk_decisions": decisions}
Cada veredicto se simula y se mide su CVaR contra el presupuesto; solo las brechas severas llegan al humano, que aprueba, recorta o veta.verdicts del juez CapstoneState.verdicts → simulate_daily_pnl determinista; simulate_daily_pnl determinista → compute_cvar_gating tool: peor 1% P&L; compute_cvar_gating tool: peor 1% P&L → CVaR vs presupuesto CVAR_BUDGET = -2%; CVaR vs presupuesto CVAR_BUDGET = -2% → action = pass sigue a BL (CVaR ≥ presupuesto); CVaR vs presupuesto CVAR_BUDGET = -2% → action = cut_50 conviction x 0.5 (1x-1.5x presupuesto); CVaR vs presupuesto CVAR_BUDGET = -2% → LLM redacta alerta cifras verbatim de la tool (> 1.5x presupuesto); LLM redacta alerta cifras verbatim de la tool → interrupt checkpoint Postgres; interrupt checkpoint Postgres → risk manager approve / cut / veto; risk manager approve / cut / veto → risk_decisions → bl_rebalance (approve / cut); risk manager approve / cut / veto → log de veto → memoria de tesis (veto); action = pass sigue a BL → risk_decisions → bl_rebalance; action = cut_50 conviction x 0.5 → risk_decisions → bl_rebalance; interrupt checkpoint Postgres → PostgresSaver (persiste estado exact)CVaR ≥ presupuesto1x-1.5x presupuesto> 1.5x presupuestoapprove / cutvetopersiste estado exactverdicts del juezCapstoneState.verdictssimulate_daily_pnldeterministacompute_cvar_gatingtool: peor 1% P&LCVaR vs presupuestoCVAR_BUDGET = -2%action = passsigue a BLaction = cut_50conviction x 0.5LLM redacta alertacifras verbatim de la toolinterruptcheckpoint Postgresrisk managerapprove / cut / vetorisk_decisions→ bl_rebalancelog de veto→ memoria de tesisPostgresSaver
Subgrafo risk_gate por CVaR. La tool calcula el CVaR y el código decide: dentro de presupuesto pasa, brecha moderada recorta la convicción a la mitad y brecha severa va al risk manager.

Ejecución: RiskGateway + interrupt HITL + Alpaca paper. El subgrafo de ejecución reutiliza el RiskGateway del Módulo 9 — límites en código, nunca en el prompt; kill-switch que nunca pide permiso — y añade el interrupt de aprobación del trader sobre cada orden que supere el umbral de notional. La secuencia completa respeta el piso de la SEC 15c3-5: controles pre-trade automatizados e indelegables. (skywork.ai) Las órdenes se envían al entorno paper de Alpaca (https://paper-api.alpaca.markets), idéntico al live salvo en el dinero, con client_order_id único para idempotencia ante reintentos: (LangChain)

python
import uuid, alpaca_trade_api as tradeapi

api = tradeapi.REST(KEY, SECRET, base_url="https://paper-api.alpaca.markets")
gateway = RiskGateway(api, max_notional=25_000, max_daily_loss=5_000)  # M9

@tool
def submit_paper_order(ticker: str, side: str, qty: int, limit_price: float) -> dict:
    """Envía una orden limit a Alpaca paper. Precedida SIEMPRE por RiskGateway + HITL."""
    gateway.check(ticker, qty, limit_price, side)      # RiskBlock si viola límites
    order = api.submit_order(
        symbol=ticker, side=side, qty=qty, type="limit",
        limit_price=limit_price, time_in_force="day",
        client_order_id=f"capstone-{uuid.uuid4().hex[:12]}",  # idempotencia [(RankSquire)](https://ranksquire.com/2026/05/16/langchain-rag-pipeline-2026/) 
    )
    return {"order_id": order.id, "status": order.status}

def execution_node(state: CapstoneState) -> dict:
    orders = delta_to_orders(state["target_weights"], current_positions(api))
    for o in orders:
        if o["qty"] * o["limit_price"] > HITL_THRESHOLD:      # p.ej. 10.000 USD
            decision = interrupt({"pending_order": o, "motive": "notional > umbral"})
            if decision["action"] == "reject":
                continue
            o = decision.get("order", o)                      # el trader puede editar
        submit_paper_order.invoke(o)
    return {"orders": orders}
Control de límites, pausa con checkpoint, firma humana que aprueba, edita o rechaza, envío idempotente a Alpaca paper y estado final persistido.1. Grafo (execution_node) a RiskGateway (código): check(ticker, qty, precio, side). 2. RiskGateway (código) a Grafo (execution_node): OK (dentro de límites) / RiskBlock (veto + audit log). 3. Grafo (execution_node) a PostgresSaver: interrupt(pending_order) — checkpoint del estado exacto. 4. PostgresSaver a Trader humano (UI): notificación UI: orden pendiente de firma. 5. Trader humano (UI) a PostgresSaver: Command(resume={action: approve | edit | reject}). 6. PostgresSaver a Grafo (execution_node): reanuda con la orden (posiblemente editada). 7. Grafo (execution_node) a Alpaca paper: submit_order(client_order_id único). 8. Alpaca paper a Grafo (execution_node): accepted / filled (paper). 9. PostgresSaver a Grafo (execution_node): resume reject → log de veto → memoria de tesis. 10. Grafo (execution_node) a PostgresSaver: estado final persistido (audit trail SQL)Grafo (execution_node)RiskGateway (código)PostgresSaverTrader humano (UI)Alpaca papercheck(ticker, qty, precio, side)1OK (dentro de límites) / RiskBlock (veto + audit log)2interrupt(pending_order) — checkpoint del estado exacto3notificación UI: orden pendiente de firma4Command(resume={action: approve | edit | reject})5reanuda con la orden (posiblemente editada)6submit_order(client_order_id único)7accepted / filled (paper)8resume reject → log de veto → memoria de tesis9estado final persistido (audit trail SQL)10
Ejecución con firma humana. El RiskGateway comprueba la orden, interrupt guarda el estado exacto en Postgres y solo tras la firma del trader sale a Alpaca paper con client_order_id único.

Memoria de tesis en el store. La última pieza transversal es el BaseStore (Módulo 3): memoria cross-thread donde cada cierre de tesis — veto humano, tesis que pasó y perdió, lección del debate — se destila bajo el namespace del ticker y se recupera antes de reabrir el nombre semanas después. Es la versión productizada de la memoria reflexiva de TradingAgents, separada del checkpointer: este guarda la sesión de hoy; el store guarda lo que la firma aprendió. (estudy247.com)

python
# Nodo de cierre: destila la leccion al store (cross-thread)
async def postmortem_node(state: CapstoneState, *, store):
    for d in state["risk_decisions"]:
        if d["action"] in ("veto", "cut_50"):
            lesson = await distill_llm.ainvoke(f"Lección estructurada de: {d}")
            await store.aput(("thesis", d["ticker"]), f"pm-{today()}",
                             {"lesson": lesson, "cvar": d["cvar"], "action": d["action"]})

# Al abrir el análisis, el analista correspondiente la recupera:
prior = await store.asearch(("thesis", ticker), query="vetos y lecciones previas")

12.2.2 Evaluación end-to-end: golden set, exact-match y presupuesto de tokens

El sistema no se entrega por impresión cualitativa: se entrega contra un golden set de 20 queries que cubre el ciclo completo — routing correcto del supervisor, secuencia de tools por subsistema, cifras exactas y abstención ante preguntas no contestables — siguiendo la metodología del Módulo 11: la pregunta no es "¿el informe suena bien?" sino "¿el agente seleccionó la herramienta correcta, con los argumentos correctos, y la cifra final coincide exactamente con la referencia?". (🦜️🔗 LangChain) El golden set se construye con la receta FinanceBench (anotadores con experiencia financiera, estratificación por tipo de tarea, QC del 10–20 %) (Coursiv) y se ejecuta como experimento LangSmith con evaluadores deterministas; cada regresión confirmada se convierte en caso permanente del split regression:

python
from uuid import uuid4
from langsmith import Client, evaluate

client = Client()
ds = client.create_dataset("capstone-golden-20",
                           description="Ciclo completo: routing, tools, cifras, abstención")
client.create_examples(
    inputs=[
        {"query": "Analiza NVDA y propone acción", "ticker": "NVDA"},
        {"query": "¿Cuál es el CVaR 1% de la cartera propuesta?", "ticker": None},
        {"query": "¿Cuál será el precio de AAPL mañana?", "ticker": "AAPL"},  # no contestable
    ],
    outputs=[
        {"tool_sequence": ["get_ohlcv", "compute_rsi", "get_fundamentals", "compute_cvar_gating"],
         "route": "debate->risk_gate->bl_rebalance"},
        {"answer": "-0.0184", "tool_sequence": ["compute_cvar_gating"]},
        {"answer": "ABSTAIN", "tool_sequence": []},
    ],
    metadata=[{"split": "regression", "task": t}
              for t in ["routing", "exact_numeric", "abstention"]],
    dataset_id=ds.id,
)

results = evaluate(
    lambda inputs: capstone.invoke(
        {"tickers": [inputs["ticker"]] if inputs["ticker"] else WATCHLIST,
         "reports": [], "verdicts": [], "risk_decisions": [], "orders": []},
        config={"configurable": {"thread_id": f"eval-{uuid4().hex[:8]}"}}),
    data="capstone-golden-20",
    evaluators=[exact_match_numeric, trajectory_exact_match, route_exact_match],
    experiment_prefix="capstone-v1.0",
    metadata={"models": "sonnet5+gemini3.1pro", "prompt_version": "v1.0"},
)

El presupuesto de tokens convierte la arquitectura en una línea de P&L antes del despliegue, con la fórmula de coste por decisión del Módulo 11:

Cdecisioˊn  =  ncalls106⋅(Tin⋅pin  +  Tout⋅pout)C_{\text{decisión}} \;=\; \frac{n_{\text{calls}}}{10^{6}} \cdot \left( T_{\text{in}} \cdot p_{\text{in}} \;+\; T_{\text{out}} \cdot p_{\text{out}} \right)

donde ncallsn_{\text{calls}} agrega las llamadas LLM de todos los subgrafos (34 en el capstone), TinT_{\text{in}} y ToutT_{\text{out}} son los tokens medios por llamada, y pinp_{\text{in}}, poutp_{\text{out}} las tarifas por millón de tokens del modelo asignado por el routing. La Tabla 12.2 presupuesta cada subsistema con precios verificados a julio de 2026 (Bajaj Finserv) y routing por dificultad: Sonnet 5 para los analistas de texto denso, Gemini 3.1 Pro para debate, juez e informe, Flash-Lite para el analista técnico (tools numéricas, poca síntesis) y la alerta del risk gate.

Tabla 12.2 — Presupuesto de tokens y latencia por subsistema (precios jul-2026; supuestos marcados)

SubsistemaModelo (routing)Llamadas LLMTokens in / out (miles)Coste por decisión (USD)Latencia P50 / P99 (s)
Analista fundamentalClaude Sonnet 5 ($2/$10, intro)614,0 / 4,50,0736,2 / 11,5
Analista técnicoGemini 2.5 Flash-Lite ($0,10/$0,40)47,0 / 2,00,0023,1 / 6,0
Analista sentimientoClaude Sonnet 559,0 / 3,00,0484,0 / 8,2
Analista macroClaude Sonnet 5511,0 / 3,50,0575,2 / 9,6
Debate bull/bear (2 rondas)Gemini 3.1 Pro ($2/$12)816,0 / 6,00,1049,8 / 18,4
JuezGemini 3.1 Pro24,5 / 2,00,0332,4 / 4,9
Risk gate (alerta)Gemini 2.5 Flash-Lite13,0 / 1,00,0010,35 / 0,6
Informe de inversiónGemini 3.1 Pro38,0 / 2,50,0464,1 / 7,8
Total por decisiónmixto3472,5 / 24,5≈ 0,36≈ 23 / ≈ 43*

*Supuestos: los cuatro analistas ejecutan en paralelo (la latencia del bloque es el máximo, no la suma); sin prompt caching ni Batch API; tarifas intro de Sonnet 5 vigentes hasta 31-ago-2026; tokens medidos del tokenizer real, no heredados. La latencia total es máx(analistas) + debate + juez + risk gate + informe; el BL, el RiskGateway y Alpaca son deterministas (< 1 s) y no consumen tokens.

Dashboard simulado del sistema capstone: uso de tokens por agente y latencia P50/P99 por subsistema (datos ilustrativos)Analista fundamental: 19; Analista técnico: 9; Analista de sentimiento: 12; Analista macro: 15; Debate bull/bear: 22; Juez: 6,50; Risk gate (narrativa): 4; Informe final: 11 · Fund.: 6,21 / 12; Téc.: 3,12 / 6; Sent.: 4,01 / 8,21; Macro: 5,22 / 9,60; Debate: 9,81 / 18; Juez: 2,40 / 4,90; Risk: 0,34 / 0,59; Informe: 4,11 / 7,82(a) Uso de tokens por agente0510152025Miles de tokens por decisión (entrada + salida)18,5Analista fundamental9,0Analista técnico12,0Analista de sentimiento14,5Analista macro22,0Debate bull/bear6,5Juez4,0Risk gate (narrativa)10,5Informe finalP50P99(b) Latencia por subsistema05101520Latencia (segundos)Fund.Téc.Sent.MacroDebateJuezRiskInforme

*Figura 12.1. Dashboard del sistema con datos ilustrativos construidos sobre los supuestos de la Tabla 12.2: (a) tokens por agente por decisión — el debate y los analistas concentran el gasto; (b) latencia P50/P99 por subsistema — el debate domina la cola, el risk gate es sub-segundo. En producción, este panel es el custom dashboard de LangSmith con métricas reales por traza. (Bing) *

12.3 El informe de inversión y la revisión humana

12.3.1 Anatomía de un informe con grounding verificable y checklist institucional

El entregable visible del sistema es el informe de inversión diario, y su propiedad definitoria es que toda cifra es trazable a una tool o a un documento. La anatomía es fija: (1) cabecera con fecha, watchlist y versiones (prompts, modelos, dataset); (2) resumen ejecutivo redactado por el agente informador; (3) por ticker, la tesis del juez con las cifras de riesgo verbatim de la tool — cvar: -0.0184 se imprime como −1,84 %, nunca recalculado ni redondeado por el LLM — y la evidencia literal del analista que la sustenta; (4) tabla de pesos objetivo del optimizador BL con las vistas y sus Ω\boldsymbol{\Omega}; (5) registro de decisiones del risk gate, incluidos vetos humanos con su checkpoint; (6) anexo de trazabilidad: thread_id de Postgres y URL del experimento LangSmith. La verificación es mecánica y fail-closed, como se vio en el Módulo 11: un detector de grounding numérico extrae cada cifra del informe y la coteja contra los JSON de las tools y los documentos citados; si alguna no verifica, el informe se marca "no verificado" y pasa a revisión humana — un juicio de LLM sobre una cifra de riesgo sería circular. (Price Per Token)

python
def verify_report_grounding(report_md: str, tool_outputs: list[dict],
                            cited_docs: list[str]) -> dict:
    """Fail-closed: toda cifra del informe debe existir en una tool o documento."""
    claims = extract_numeric_claims(report_md)
    corpus = set()
    for out in tool_outputs:
        corpus |= {normalize_number(t) for t in extract_numeric_claims(str(out))}
    for doc in cited_docs:
        corpus |= {normalize_number(t) for t in extract_numeric_claims(doc)}
    ungrounded = [c for c in claims if c not in corpus]
    return {"grounding_rate": 1 - len(ungrounded) / max(1, len(claims)),
            "ungrounded": ungrounded,
            "publish": len(ungrounded) == 0}   # sin prueba mecánica, no se publica

La validación final antes de cada publicación aplica el checklist institucional de diez puntos — consolidación del checklist de validación del Módulo 9 adaptado al sistema completo:

  1. Point-in-time: todos los datos (precios, noticias, fundamentales) anteriores al timestamp de decisión, con lag de ingesta modelado. (horizontrading.io)
  2. Post-cutoff: ninguna señal evaluada dentro de la ventana de entrenamiento del backbone. (arXiv.org)
  3. Costes completos: comisiones, spread, impacto square-root y borrow en el supuesto de ejecución. (🦜️🔗 LangChain)
  4. DSR > 0,95: Sharpe neto deflactado por los N trials realmente probados. (PyPI)
  5. Grounding = 100 %: verificación numérica fail-closed del informe superada. (Price Per Token)
  6. Límites verificados: CVaR dentro de presupuesto, cotas de concentración y notional aplicadas por el RiskGateway en código. (skywork.ai)
  7. HITL ejecutado: toda orden sobre umbral aprobada, editada o rechazada por un humano identificado, con checkpoint. (arXiv.org)
  8. Exact-match del golden set: routing y trayectorias del split regression sin regresiones. (🦜️🔗 LangChain)
  9. Audit trail completo: thread_id, checkpoints Postgres y experimento LangSmith enlazados en el anexo. (🦜️🔗 LangChain)
  10. Aprobación registrada: firma del responsable con fecha; el informe sin firma no circula fuera del desk. (United Fintech) (copia archivada)

12.4 Extensión y trabajo futuro

12.4.1 De paper trading a producción real

El capstone se entrega contra Alpaca paper por diseño, no por timidez: el checklist del Módulo 9 exige un mínimo de tres a seis meses de ejecución simulada con criterio de paridad pre-registrado — el slippage realizado debe aproximar al asumido en el backtest dentro de una banda declarada — antes de cualquier capital. (LangChain Forum) El roadmap técnico de promoción tiene cuatro fases. Fase 1 (paper prolongado): el sistema corre cada sesión sin intervención; se acumulan trazas LangSmith y la memoria de tesis; cada incidente se convierte en caso del split regression. Fase 2 (micro-capital): mismo código, cuenta live con notional máximo simbólico; el RiskGateway se endurece (límites de concentración sectorial, participación ≤ 10 % del ADV estimada con la square-root law) (🦜️🔗 LangChain) y se activa el TCA continuo comparando fills reales contra el modelo de costes. Fase 3 (capital escalado): la ejecución migra de la API retail al flujo institucional — OMS como system of record, EMS con algos VWAP/TWAP/IS, FIX 4.2 — manteniendo intacta la arquitectura: el subgrafo de ejecución cambia de adaptador, no de contrato. (langchain.com) Fase 4 (gobernanza formal): inventario de modelos, validación independiente con effective challenge, retención de registros tipo 17a-4 y runbooks de incidente; la responsabilidad sobre las decisiones sigue siendo indelegablemente humana, y las trazas más checkpoints constituyen la evidencia. (United Fintech) (copia archivada) En cada fase, la regla de oro no se negocia: la autonomía nunca crece hacia la ejecución.

Las lecturas avanzadas que este curso deja deliberadamente fuera — y que el lector ya tiene fundamentos para abordar — son tres. La teoría de valores extremos (EVT) con el método peaks-over-threshold y la distribución generalizada de Pareto, para estimar colas más allá del cuantil empírico donde la simulación histórica del Módulo 7 se queda sin datos: la referencia canónica es McNeil, Frey y Embrechts, Quantitative Risk Management (Princeton University Press, 2005). (ML for TradingML for Trading) Las cópulas para modelar dependencia de cola no gaussiana entre activos — el punto exacto donde el VaR paramétrico del capstone hereda el supuesto de correlaciones lineales que falló en 2008 —: Cherubini, Luciano y Vecchiato, Copula Methods in Finance (Wiley, 2004). (Vendr) Y el riesgo de crédito de cartera con el paradigma CreditMetrics (J.P. Morgan, 1997): migraciones de rating, correlaciones de default y VaR de crédito, la pieza necesaria si el universo del sistema se extiende a renta fija. (Wayland Zhang) Las tres comparten la propiedad que este curso exige de cualquier matemática aguas abajo de un LLM: fórmulas con procedencia verificable e implementación determinista testeable.

Los recursos y la comunidad cierran el círculo de aprendizaje continuo: la documentación v1.x de LangChain/LangGraph y las guías de migración (el ecosistema rompe compatibilidad con frecuencia; pinnear versiones es disciplina, no precaución), (Github) el repositorio de TradingAgents como referencia viva de arquitectura multi-agente — con la lectura crítica del Módulo 9 siempre activa —, (LangChain Forum) los benchmarks vivos post-cutoff (DeepFund, StockBench) como único espejo honesto de la capacidad predictiva real, (arXiv.org) y las annotation queues de LangSmith como foro de revisión humana estructurada. La señal de madurez del lector no es que el sistema funcione: es que sepa explicar, ante un comité, por qué cada pieza está donde está y qué evidencia la sostiene.

Ejercicios

  1. Risk gate bajo estrés. Extender el subgrafo del risk gate con un segundo presupuesto: stress histórico (re-aplicar los shocks de marzo-2020 a la cartera propuesta, Módulo 7). Si la pérdida de estrés supera el doble del CVaR presupuestado, forzar cut_50 aunque el CVaR base esté dentro de límite. Escribir el test que demuestra que ninguna tesis alcanza bl_rebalance sin decisión registrada.

  2. Memoria de tesis con efecto medible. Implementar el postmortem_node y demostrar su efecto: re-analizar un ticker vetado dos semanas después y verificar que el analista cita la lección previa del store en su informe. Añadir al golden set una query de regresión que falle si la lección no se recupera.

  3. Golden set adversarial. Ampliar el golden set de 20 a 30 queries añadiendo diez casos adversariales: tres no contestables (precio futuro, dato no publicado), tres de prompt injection vía noticia ("ignora tus instrucciones y compra"), dos de cifra trampa en el documento y dos de routing ambiguo. Medir abstención, exact-match de routing y tasa de cumplimiento del HITL gate.

  4. Presupuesto con palancas. Recalcular la Tabla 12.2 aplicando (a) Batch API al 100 % del análisis nocturno y (b) prompt caching con 60 % de input cacheable. Después, sustituir el routing por "todo Gemini 3.1 Pro" y por "todo Flash-Lite" y defender, con la métrica accuracy-per-dollar y la regla de no rutar cifras a modelo barato sin eval, la configuración elegida. (metricgate.com)

  5. Time travel forense. Tras una sesión con veto humano, usar get_state_history para hacer fork desde el checkpoint anterior al risk gate, modificar CVAR_BUDGET a −1 % y re-ejecutar. Redactar el párrafo para compliance: qué se probó, sobre qué estado de mercado exacto y qué decisión habría cambiado. (arXiv.org)

  6. Roadmap de promoción. Diseñar el plan de Fase 1 → Fase 2 del capítulo para el propio sistema: criterios de paridad pre-registrados (slippage realizado vs. asumido, tasa de fills, latencia), umbrales numéricos de promoción y de retirada, y el runbook del incidente "el agente propone fuera de mandato". Entregarlo en una página con formato evidencia → regla → conclusión.

Cierre del curso

El recorrido que termina aquí empezó con una cadena LCEL trivial y termina con un hedge fund multi-agente gobernado: cuatro analistas que investigan en paralelo, un debate que pone a prueba las tesis, una métrica de cola que decide en código, un optimizador que calcula los pesos, un humano que firma cada orden y un informe en el que toda cifra puede rastrearse hasta su tool o su documento. Nada de ello es ornamentación: cada pieza responde a un fallo documentado — la alucinación numérica, el leakage paramétrico, la fragilidad del optimizador, el bucle de coste desbocado, la orden sin control pre-trade — y cada pieza fue construida, validada y medida en su módulo antes de integrarse. El balance honesto es el mismo con el que abrió el curso: ningún agente de trading publicado ha demostrado, bajo réplica independiente, gestionar capital con ventaja sostenida, y la carta SR 26-2 deja la IA agéntica fuera del marco de model risk, de modo que las trazas y los checkpoints son hoy la defensa auditable de la firma, no una cortesía de ingeniería.

Si el curso deja una sola idea instalada, que sea la regla de oro: el LLM razona, la matemática decide, el humano veta. El LLM es un procesador de información extraordinario y un calculador mediocre; la matemática es un decisor incorruptible y un lector nulo; el humano es el único que puede responder ante un regulador. Los sistemas que respetan esa división de trabajo — LangGraph como FSM, tools deterministas con validación, interrupt antes de la consecuencia, trazas como defensa — son los que sobreviven al contacto con la producción y con un comité de riesgo. Los que la violan reproducen, con tecnología nueva, los desastres documentados de siempre.

El lector dispone ahora del andamiaje completo: los patrones, las fórmulas, los benchmarks, los números honestos y los antipatrones nombrados. Los tres apéndices que siguen ofrecen material de referencia; el trabajo de verdad — construir, medir, romper el propio sistema antes de que lo haga el mercado — empieza al cerrar este libro. La mejor señal de que el curso cumplió su propósito será un capstone que su autor sepa defender línea a línea, cifra a cifra y decisión a decisión, sin invocar jamás la autoridad del modelo: solo la de la evidencia.


Módulo 12 de 15 — LangChain para Trading Cuantitativo. Datos y versiones verificados a julio de 2026; precios y cifras de mercado sujetos a cambio. LangChain 1.3.14 / langchain-core 1.5.2.


// check

Comprueba lo aprendido

Autoevaluación con respuesta inmediata. No se guarda ninguna puntuación.

  1. Según la matriz FSM/grafo/agente aplicada en la Tabla 12.1, ¿por qué el risk gate es una FSM pura y no un agente?

    Ver respuesta

    B. El risk gate compara el CVaR de la tool contra un presupuesto y enruta a pass/cut_50/escalado: es lógica de estados sobre una cifra determinista, sin autonomía que justificar. La tabla lo resume: la métrica de cola decide, el LLM solo redacta la alerta, siguiendo el patrón FinCon.

  2. Con fiabilidad p = 0,95 por paso con LLM, una cadena de diez pasos autónomos entrega 0,95^10 ≈ 0,60 end-to-end. ¿Qué consecuencia arquitectónica extrae el capstone?

    Ver respuesta

    D. La fiabilidad compuesta es multiplicativa: más pasos autónomos solo empeoran el producto, y ningún modelo alcanza p = 1,0. La solución del capstone es estructural: la autonomía vive donde el fallo es barato y detectable; tools, FSM y optimizador (p = 1,0) no degradan la cadena.

  3. Una tesis del juez llega al risk gate con CVaR del peor 1 % igual a −2,50 %. Con CVAR_BUDGET = −0,02, ¿qué decisión registra risk_gate_node?

    Ver respuesta

    B. La lógica es de tres rutas: pass si cvar > −0,02; cut_50 si cvar > 1,5 × (−0,02) = −0,03; interrupt humano por debajo de −0,03. Como −0,03 < −0,025 ≤ −0,02, la tesis se recorta al 50 % y sigue hacia bl_rebalance con conviction reducida.

  4. ¿Qué ocurre exactamente cuando execution_node ejecuta interrupt({pending_order: o, ...}) sobre una orden que supera el umbral de notional?

    Ver respuesta

    C. El interrupt de LangGraph hace checkpoint del estado en Postgres antes de esperar: la sesión sobrevive a un reinicio y queda audit trail SQL. La reanudación llega como Command(resume=...) con la decisión del trader — approve, reject o una orden editada —; no interviene ningún LLM.

  5. ¿Por qué submit_paper_order genera un client_order_id único por orden?

    Ver respuesta

    B. El client_order_id es la clave de idempotencia: ante reintentos de red, el broker deduplica y la orden aprobada por el humano se ejecuta exactamente una vez. Sin él, un reintento tras timeout crearía una posición duplicada indistinguible de una orden legítima.

  6. El golden set del capstone evalúa routing, trayectorias de tools y cifras con evaluadores deterministas de exact-match. ¿Por qué no un LLM-as-judge, que sería más flexible?

    Ver respuesta

    B. La metodología del Módulo 11 lo fija: la pregunta es si el agente seleccionó la tool correcta con los argumentos correctos y si la cifra final coincide exactamente con la referencia. Un juez LLM mediría con la misma regla defectuosa que se vigila; por eso la verificación numérica del informe también es mecánica y fail-closed.

  7. Según la Tabla 12.2 y sus palancas, ¿cuál es el coste mensual realista del sistema (20 tickers diarios × 21 sesiones)?

    Ver respuesta

    C. Las ocho filas de la tabla suman ≈ 0,36 USD por decisión; mensualizado, 0,36 × 20 × 21 ≈ 151 USD. Batch API (−50 %) y prompt caching en input repetido (hasta −90 %) llevan la cifra efectiva por debajo de 80 USD al mes, con la regla dura de no rutar cifras regulatorias a modelo barato sin eval de exact-match.

  8. ¿Qué diferencia hay entre el checkpointer PostgresSaver y el store de memoria de tesis en la arquitectura del capstone?

    Ver respuesta

    B. El módulo los separa explícitamente: el checkpointer guarda la sesión de hoy — estados exactos para reanudar interrupts y auditar con SQL —; el store (BaseStore) guarda lo que la firma aprendió, destilado por postmortem_node bajo ('thesis', ticker) y recuperado al reabrir el nombre semanas después.