// SEÑALES · MÓDULO 09/12

Señales LLM y backtesting riguroso

Del texto a la señal, backtests sin look-ahead, costes realistas y deflated Sharpe ratio.

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


Los módulos 7 y 8 dotaron al lector del marco de riesgo y de la construcción de carteras con vistas Black–Litterman. Este módulo cierra el Bloque III recorriendo el camino completo que va de la señal a la orden: qué dice realmente la literatura de agentes LLM de trading, cómo se convierte una salida de lenguaje natural en una señal cuantitativa defendible, cómo se valida con los estándares que exigiría un comité de asignación, y cómo se conecta a la ejecución sin ceder el control a un sistema estocástico. El principio rector, ya anticipado en módulos anteriores, se mantiene intacto: el LLM razona, la matemática decide y el humano veta; los papers de agentes se tratan aquí como objeto de estudio, no como estrategia desplegable (ProfitLogic).

9.1 El estado del arte de los agentes de trading

9.1.1 Panorama verificado: arquitecturas, números estrella y sesgos documentados

Entre 2023 y 2026 la literatura académica produjo una genealogía reconocible de agentes LLM para trading: FinMem (arXiv 2311.13743, AAAI Symposium 2024) introdujo la memoria estratificada por horizonte temporal —capas shallow (noticias diarias, alto decay), intermediate (10-Q) y deep (10-K y reflexiones, bajo decay)— con un perfil de riesgo conmutable, y reportó Sharpe > 2,0 y retornos acumulados > 0,35 en TSLA y NFLX sobre cinco mega-caps en 2021–2022 (pfolio.io). FinAgent (arXiv 2402.18485, KDD 2024) añadió multimodalidad (texto, series numéricas, gráficos K-line) y doble reflexión; su titular de abstract —92,27 % de retorno— procede de un dataset entre seis, el patrón exacto de selección del mejor resultado (arXiv.org). TradingAgents (arXiv 2412.20138; repo con más de 65.000 estrellas) simula una firma completa —cuatro analistas, investigadores bull/bear en debate, trader, comité de riesgo, fund manager— y reporta CR 26,62 % y Sharpe 8,21 en AAPL en una ventana de tres meses de 2024, con máximos drawdowns de 0,91 % (🦜️🔗 LangChain). Los propios autores añaden un pie autocrítico inusualmente honesto: el Sharpe excede su rango empírico esperado (SR > 3 "excelente") y lo atribuyen a la ausencia de correcciones en la ventana; con 11 llamadas LLM y más de 20 llamadas a herramientas por predicción, solo pudieron testear tres meses (langchain.js).

StockAgent (arXiv 2407.18957, ACM TIST) aporta la contribución metodológica de nombrar el leakage ya en el abstract: es una simulación con order book y personalidades de inversor diseñada explícitamente para impedir que el modelo explote conocimiento previo del periodo de test (Springer). FinRobot (arXiv 2405.14767, AI4Finance) es una plataforma de análisis en cuatro capas con Smart Scheduler multi-LLM, no un sistema de trading; su valor es arquitectónico, y sus autores critican la obsesión del campo con el backtesting frente a enfoques orientados a proceso (rdrr.io). FinCon (arXiv 2407.06567, NeurIPS 2024 main track) implementa jerarquía manager–analyst con refuerzo verbal conceptual (CVRF) y gating de riesgo con CVaR, reportando CR 113,84 % y Sharpe 3,269 en una cartera TSLA/MSFT/PFE, y siendo el único agente con retorno positivo en el estrés de TSLA-2022; también admite alucinaciones en la gestión de memoria (Github). MarketSenseAI (arXiv 2401.03737, ESWA) reporta batir al S&P 100 por 13 puntos con retornos de hasta 40 % en 2023 —año dentro de la ventana de conocimiento de GPT-4— y declara un conflicto de interés: los autores comercializan el framework como startup (skfolio.org). HedgeAgents (arXiv 2502.13165, WWW 2025 Companion) reporta 70 % anualizado y 400 % en tres años, y en el mismo abstract admite que los agentes LLM convencionales pierden ~−20 % en caídas rápidas (arXiv.org).

Tabla 9.1 — Agentes LLM de trading publicados: arquitectura, resultado reportado y sesgos documentados

Paper (venue)Arquitectura núcleoResultado reportadoSesgos y advertencias documentadas
FinMem (AAAI Symp. 2024)Memoria por capas + carácter conmutableSR > 2,0, CR > 0,35 (TSLA, NFLX) (pfolio.io)5 mega-caps, 2021–22; sin costes; regla de conmutación de riesgo ajustada ex post; PC = 0,8213 de leakage contrafáctico (arXiv.org)
FinAgent (KDD 2024)Multimodal + doble reflexión92,27 % retorno (1 dataset de 6) (arXiv.org)Selección del mejor resultado en el abstract; recuperación por similitud pura
TradingAgents (arXiv, >65k★)Firma multi-agente bull/bear/riskCR 26,62 %, SR 8,21 (AAPL) (🦜️🔗 LangChain)Ventana 3 meses; datos dentro del cutoff de GPT-4; autores: SR "fuera de rango empírico" (langchain.js)
StockAgent (ACM TIST)Simulación order book anti-leakageN/A (comportamental) (Springer)No es estrategia; el comportamiento refleja al backbone, no al mercado
FinCon (NeurIPS 2024)Manager–analyst + CVRF + CVaRCR 113,84 %, SR 3,269 (Github)Ventana oct-2022→jun-2023; admite alucinaciones de memoria (Github); costes parciales
MarketSenseAI (ESWA 2024)GPT-4 CoT stock selection+13 % vs S&P 100, hasta 40 % (skfolio.org)Test 2023 in-cutoff; conflicto de interés comercial declarado (Advisor Perspectives)
HedgeAgents (WWW 2025)Fund manager + expertos hedge70 % anualizado, 400 %/3 años (arXiv.org)Sin réplica independiente; el propio paper admite −20 % en estrés (arXiv.org)

9.2 El sentimiento como señal cuantitativa

9.2.1 Lopez-Lira & Tang, la evidencia PEAD y el pipeline de integración

La señal LLM con mejor evidencia no es una arquitectura de agentes sino un clasificador de titulares. Lopez-Lira y Tang (arXiv 2304.07619) puntuaron con ChatGPT todos los titulares de valores comunes de CRSP entre octubre de 2021 y diciembre de 2022 —posterior al cutoff de ChatGPT, control de contaminación explícito— y construyeron una estrategia long-short autofinanciada: Sharpe 3,8 con GPT-4 en la versión v4 del paper, degradado a 3,28 en la v5 (y GPT-3.5 de 3,1 a 1,79), frente a Sharpes negativos de GPT-1/2 y BERT (arXiv.org). El retorno acumulado supera el 500 %, pero sin costes de transacción, y con una asimetría reveladora: la pierna corta aporta 29 bps/día frente a 9 bps/día de la larga —la señal es sobre todo un detector de malas noticias, concentrada en small-caps donde los límites al arbitraje encarecen la corrección (arXiv.org). Que los números cambien entre versiones del mismo paper es en sí mismo una lección metodológica sobre la fragilidad de los backtests. La evidencia PEAD es complementaria y de pedigrí sólido: features contextuales de earnings calls mejoran el retorno por trade de una cartera long-short en 53–354 bps out-of-sample sobre un universo S&P 500 point-in-time 2010–2022 (fgv.br), aunque existen contraejemplos honestos donde el texto solo no supera al baseline (Advisor Perspectives).

La integración profesional trata el score del LLM como un factor más, no como una orden. El pipeline de referencia: (1) el LLM emite scores continuos (preferibles a etiquetas discretas, porque preservan intensidad); (2) agregación temporal con decay exponencial —la predictibilidad documentada es a 1–5 días— y ponderación por novedad, descontando titulares duplicados o republicados; (3) winsorización y z-score cross-sectional por fecha; (4) neutralización frente a sector y factores de riesgo (mercado, tamaño, momentum), quedándose con el residual; (5) combinación IC-weighted con el alfa existente; (6) ejecución con lag de ingesta modelado, porque los timestamps de las noticias registran publicación, no disponibilidad para el pipeline (horizontrading.io).

zi,t  =  si,t−mediana⁡j(sj,t)1,4826⋅MAD⁡j(sj,t),s~i,t  =  ∑k=0Kwk si,t−k,wk∝e−kln⁡2/hz_{i,t} \;=\; \frac{s_{i,t} - \operatorname{mediana}_j(s_{j,t})}{1{,}4826 \cdot \operatorname{MAD}_j(s_{j,t})}, \qquad \tilde{s}_{i,t} \;=\; \sum_{k=0}^{K} w_k\, s_{i,t-k}, \quad w_k \propto e^{-k \ln 2 / h}

donde si,ts_{i,t} es el score agregado de noticias del activo ii y hh es la vida media de la señal (1–3 días en la práctica documentada). La neutralización se implementa como regresión cross-sectional diaria del z-score sobre dummies sectoriales y exposiciones factoriales, usando el residual como señal final.

Pipeline de señal: score LLM, decay exponencial, z-score, neutralización y backtest con costes; si la validación falla, la señal se descarta o reformula.Noticias / filings timestamp + lag de ingesta → LLM scoring score continuo -1..1; LLM scoring score continuo -1..1 → Agregación temporal decay exponencial h=2d peso por novedad; Agregación temporal decay exponencial h=2d peso por novedad → Z-score cross-sectional winsorizado 1%/99%; Z-score cross-sectional winsorizado 1%/99% → Neutralización sector + factores → residual; Neutralización sector + factores → residual → Backtest con costes spread + impacto sqrt + borrow; Backtest con costes spread + impacto sqrt + borrow → Validación: DSR>0.95 PBO bajo, walk-forward; Validación: DSR>0.95 PBO bajo, walk-forward → Paper trading ≥3 meses (aprueba); Validación: DSR>0.95 PBO bajo, walk-forward → Descartar / reformular (rechaza)apruebarechazaNoticias / filingstimestamp + lag de ingestaLLM scoringscore continuo -1..1Agregación temporaldecay exponencial h=2dpeso por novedadZ-score cross-sectionalwinsorizado 1%/99%Neutralizaciónsector + factores → residualBacktest con costesspread + impacto sqrt + borrowValidación: DSR>0.95PBO bajo, walk-forwardPaper trading ≥3 mesesDescartar / reformular
De score LLM a factor cuantitativo. El score del LLM se agrega con decay, se normaliza y se neutraliza antes del backtest con costes; solo lo que supera la validación pasa a paper trading.
python
# --- Pipeline señal sentimiento LLM → factor cuantitativo (pandas) ---
import numpy as np, pandas as pd

def agregar_decay(scores: pd.DataFrame, halflife: float = 2.0) -> pd.DataFrame:
    """scores: DataFrame [fechas x tickers] de scores LLM diarios (NaN = sin noticia).
    Decay exponencial con vida media `halflife` días; solo días con noticia pesan."""
    lam = np.log(2) / halflife
    out = scores.copy().astype(float)
    for t in range(1, len(out)):
        out.iloc[t] = out.iloc[t].fillna(0) + np.exp(-lam) * out.iloc[t-1].fillna(0)
    return out

def zscore_cross_sectional(sig: pd.DataFrame, win: float = 0.01) -> pd.DataFrame:
    lo, hi = sig.quantile(win, axis=1), sig.quantile(1 - win, axis=1)
    w = sig.clip(lo, hi, axis=0)                      # winsorizado por fecha
    med = w.median(axis=1)                            # mediana cross-sectional
    mad = w.sub(med, axis=0).abs().median(axis=1)     # MAD cross-sectional
    return w.sub(med, axis=0).div(1.4826 * mad.replace(0, np.nan), axis=0)

def neutralizar(z: pd.DataFrame, sectores: pd.Series) -> pd.DataFrame:
    """Residual de z sobre dummies sectoriales, por fecha (OLS cross-sectional)."""
    D = pd.get_dummies(sectores)
    def resid(fila):
        y = fila.dropna(); Xd = D.loc[y.index]
        beta, *_ = np.linalg.lstsq(Xd.values, y.values, rcond=None)
        return y - Xd.values @ beta
    return z.apply(resid, axis=1)

9.3 Backtesting con estándares institucionales

9.3.1 Frameworks 2026, trampas y corrección estadística

La decisión arquitectónica primera no es qué librería sino qué familia: los motores vectorizados computan la señal sobre arrays completos y simulan fills aproximados al cierre de barra; los motores event-driven procesan eventos ordenados en el tiempo y la estrategia reacciona —fidelidad de ejecución alta, velocidad menor (CSDN博客). La regla de decisión 2026: descubrimiento masivo de señales en vectorbt/vectorbtpro; validación de microestructura y camino research→live en nautilus_trader o QuantConnect LEAN; factores cross-sectionales en zipline-reloaded (Pipeline API); asignación por pesos en bt (ar5iv).

Tabla 9.2 — Frameworks de backtesting 2026: arquitectura, velocidad, paridad backtest→live y mantenimiento

FrameworkArquitectura / núcleoVelocidadMulti-activoParidad backtest→liveMantenimiento (2026)
vectorbt (OSS) / vectorbtproVectorizado NumPy/Numba; pro añade loops NumbaMuy alta–extremaSí (columnas = activos)No nativa ("execute it elsewhere") (Advisor Perspectives)OSS comunitario; pro activo por suscripción (desde ~25 USD/mes) (ar5iv)
zipline-reloadedEvent-driven + Pipeline API (Python)Baja-mediaSí, cross-sectionalNo (solo research)Comunidad; ritmo "glacial" (quant67.com)
backtraderEvent-driven loop, Python puroBaja (single-threaded)SíIntegraciones envejecidasLegacy: proyecto estancado (ar5iv)
QuantConnect LEANEvent-driven, C# + wrapper PythonAlta (10 años equities ≈ 33 s cloud) (MDPI)Sí: equities, opciones, futuros, FX, criptoNativa: mismo código, 20+ brokersMuy activo (16.000+ estrellas) (MDPI)
nautilus_traderEvent-driven actor/bus, núcleo Rust + PyO3Muy altaAsset-class agnostic, multi-venueNativa: código idéntico, solo cambia configuración (CSDN博客)Muy activo (Nautech Systems)
btMotor de pesos/rebalanceo (Python)MediaSí (cestas ETF/factores)NoMaduro/estable, baja actividad (ar5iv)

Las cuatro trampas canónicas tienen cada una su detector. Look-ahead: usar información no disponible en el momento de la decisión —incluido el caso LLM, donde la fuga está en los pesos del modelo— (臺灣博碩士論文知識加值系統). Survivorship: universos sin delistings, con distorsiones anuales estimadas entre el 0,1 % y el 0,9 % y preinclusion bias al usar constituyentes actuales del índice (臺灣博碩士論文知識加值系統). Data snooping / multiple testing: probar muchas configuraciones sobre los mismos datos y reportar la mejor; el Sharpe reportado es casi nunca una hipótesis única sino el máximo de muchas estimaciones ruidosas (RDocumentation). Selección/reporte: McLean y Pontiff documentaron degradaciones medias del −26 % out-of-sample y −58 % post-publicación en predictores publicados (Github).

La corrección estadística canónica es el Probabilistic Sharpe Ratio, que ajusta el Sharpe estimado por no-normalidad y longitud de muestra con el error estándar de Lo:

PSR^(SR∗)  =  Φ ⁣((SR^−SR∗)T−1 1−γ^3 SR^+γ^4−14SR^2 )\widehat{PSR}(SR^*) \;=\; \Phi\!\left( \frac{(\widehat{SR}-SR^*)\sqrt{T-1}}{\sqrt{\,1-\hat{\gamma}_3\,\widehat{SR}+\frac{\hat{\gamma}_4-1}{4}\widehat{SR}^{2}\,}} \right)

donde TT es el número de observaciones, γ^3\hat{\gamma}_3 la asimetría muestral, γ^4\hat{\gamma}_4 la curtosis (3 = Normal) y Φ\Phi la CDF Normal. El Deflated Sharpe Ratio sustituye el benchmark SR∗SR^* por el máximo esperado bajo la hipótesis nula de NN estrategias intentadas sin skill:

SR0  =  V ⁣[{SR^k}k=1..N]  [ (1−γEM) Φ−1 ⁣(1−1N)  +  γEM Φ−1 ⁣(1−1N e)]SR_0 \;=\; \sqrt{\mathbb{V}\!\left[\{\widehat{SR}_k\}_{k=1..N}\right]}\;\Big[\,(1-\gamma_{EM})\,\Phi^{-1}\!\Big(1-\tfrac{1}{N}\Big) \;+\; \gamma_{EM}\,\Phi^{-1}\!\Big(1-\tfrac{1}{N\,e}\Big)\Big]

con γEM≈0,5772\gamma_{EM} \approx 0{,}5772 (constante de Euler–Mascheroni) y V[⋅]\mathbb{V}[\cdot] la varianza de los Sharpes de las NN configuraciones probadas. Entonces DSR^=PSR^(SR0)\widehat{DSR} = \widehat{PSR}(SR_0), con regla operativa DSR > 0,95: incluso admitiendo cuántas estrategias se probaron, hay un 95 %+ de probabilidad de que el edge sea real (PyPI). Complementos: la PBO (Probability of Backtest Overfitting) vía CSCV —partir la matriz de P&L en SS bloques, tomar las (SS/2)\binom{S}{S/2} combinaciones mitad IS / mitad OOS, seleccionar la configuración óptima IS y medir su rango OOS— es la probabilidad de que la mejor configuración in-sample quede por debajo de la mediana out-of-sample, y tiende a 1 cuando NN crece con independencia de que exista poder predictivo genuino (PyPI). El haircut de Harvey–Liu convierte el Sharpe a t-ratio, ajusta el p-valor por el número de tests (Bonferroni, Holm, BHY) y re-convierte; es marcadamente no lineal —la regla del "50 % de haircut" es un error; Sharpe 1,0 con 120 meses y 100 tests queda en 0,438 (BHY) o 0,232 (Bonferroni)— (博客园):

HC  =  1−E[ SROOS ]SRISHC \;=\; 1-\frac{\mathbb{E}[\,SR_{OOS}\,]}{SR_{IS}}
python
# --- Deflated Sharpe Ratio (Bailey & López de Prado 2014) ---
import numpy as np
from scipy.stats import norm

def probabilistic_sharpe(sr, sr_star, n_obs, skew, kurt):
    se = np.sqrt(1 - skew * sr + (kurt - 1) / 4 * sr**2)   # error estándar de Lo (2002)
    return norm.cdf((sr - sr_star) * np.sqrt(n_obs - 1) / se)

def expected_max_sharpe(n_trials, var_sr, euler=0.5772156649):
    return np.sqrt(var_sr) * ((1 - euler) * norm.ppf(1 - 1 / n_trials)
                              + euler * norm.ppf(1 - 1 / (n_trials * np.e)))

def deflated_sharpe(sr, n_obs, skew, kurt, n_trials, var_sr):
    return probabilistic_sharpe(sr, expected_max_sharpe(n_trials, var_sr),
                                n_obs, skew, kurt)

# Sharpe 1.2 anual, 3 años diarios, 100 trials, skew=-1, kurt=6
dsr = deflated_sharpe(1.2/np.sqrt(252), 756, -1.0, 6.0, 100, 0.10/252)
print(f"DSR = {dsr:.3f} -> {'edge plausible' if dsr > 0.95 else 'indistinguible de ruido'}")
python
# --- Backtest con costes en vectorbt (API real) ---
import vectorbt as vbt

data  = vbt.YFData.download("AAPL", start="2020-01-01", end="2023-01-01")
close = data.get("Close")
fast, slow = vbt.MA.run(close, 20), vbt.MA.run(close, 50)

pf = vbt.Portfolio.from_signals(
    close,
    entries=fast.ma_crossed_above(slow),
    exits=fast.ma_crossed_below(slow),
    init_cash=10_000,
    fees=0.001,        # 0,1% comisión por lado
    slippage=0.001,    # 0,1% slippage por lado
    freq='D',
)
print(pf.stats())      # comparar con fees=0, slippage=0 para cuantificar el drag

9.3.2 Costes de transacción realistas y capacidad

La medida canónica de coste de ejecución es el implementation shortfall (Perold 1988): la brecha entre el precio a la llegada de la orden y el precio efectivamente conseguido (LangChain):

IS  =  12 s⏟spread  +  εlat+εadv⏟slippage  +  MI(Q)⏟impacto  +  cbr+cexch+creg⏟comisiones y tasas  +  borrow⏟preˊstamo (cortos)  +  OC⏟oportunidadIS \;=\; \underbrace{\tfrac{1}{2}\,s}_{\text{spread}} \;+\; \underbrace{\varepsilon_{\text{lat}} + \varepsilon_{\text{adv}}}_{\text{slippage}} \;+\; \underbrace{MI(Q)}_{\text{impacto}} \;+\; \underbrace{c_{\text{br}} + c_{\text{exch}} + c_{\text{reg}}}_{\text{comisiones y tasas}} \;+\; \underbrace{b_{\text{orrow}}}_{\text{préstamo (cortos)}} \;+\; \underbrace{OC}_{\text{oportunidad}}

El componente más olvidado en los papers LLM es el borrow en cortos: el vendedor en corto paga una tasa anual sobre el valor de la posición, con coste diario

Cborrow,dıˊa  =  rb360⋅Pt⋅QshortC_{\text{borrow,día}} \;=\; \frac{r_b}{360}\cdot P_t \cdot Q_{\text{short}}

donde rbr_b es el borrow fee anual. Magnitudes reales: un ETF general collateral como SMH cotizaba a 0,7 % anual (IBKR, jul-2026); los valores hard to borrow superan el 10–100 % anual e inviabilizan estrategias de cortos de alta rotación (CSDN博客). Recordando que la pierna corta aportaba el triple que la larga en Lopez-Lira & Tang (arXiv.org), omitir el borrow no es un detalle: es omitir el coste principal de la señal.

El modelo puente teoría-práctica es Almgren–Chriss (2001): liquidar XX acciones en [0,T][0,T] con NN pasos de longitud τ=T/N\tau = T/N, precio con impacto permanente γ\gamma y temporal η\eta, minimizando E(x)+λV(x)E(x) + \lambda V(x) con

E(x)  =  12γX2+ε∑j=1N∣nj∣+η~τ∑j=1Nnj2,V(x)  =  σ2 τ∑j=1Nxj2E(x) \;=\; \tfrac{1}{2}\gamma X^{2} + \varepsilon \sum_{j=1}^{N}|n_j| + \frac{\tilde{\eta}}{\tau}\sum_{j=1}^{N} n_j^{2}, \qquad V(x) \;=\; \sigma^{2}\,\tau \sum_{j=1}^{N} x_j^{2}

La solución cerrada es una trayectoria hiperbólica con parámetro de urgencia κ\kappa (Blockchain News):

xj  =  sinh⁡ ⁣(κ (T−tj))sinh⁡(κT)  X,κ  ∼  λ σ2η  +  O(τ),τ→0x_j \;=\; \frac{\sinh\!\big(\kappa\,(T-t_j)\big)}{\sinh(\kappa T)}\; X, \qquad \kappa \;\sim\; \sqrt{\frac{\lambda\,\sigma^{2}}{\eta}} \;+\; O(\tau), \quad \tau \to 0

Dos límites docentes: con κT≪1\kappa T \ll 1 la trayectoria degenera en TWAP lineal (minimiza impacto); con κT≫1\kappa T \gg 1 la liquidación es front-loaded exponencial con vida media θ=1/κ\theta = 1/\kappa. Y como la frontera eficiente es diferenciable en el punto de coste mínimo, la estrategia ingenua (λ=0\lambda = 0) nunca es óptima para un trader averso al riesgo: reducciones significativas de varianza cuestan solo incrementos marginales de coste esperado (Github).

Sobre el impacto, la ley empírica dominante para metaórdenes es la square-root law (🦜️🔗 LangChain):

I(Q)  =  Y  σD  (QVD)δ,δ≈12,    Y≈0,3 ⁣− ⁣1I(Q) \;=\; Y\; \sigma_{D}\; \left(\frac{Q}{V_{D}}\right)^{\delta}, \qquad \delta \approx \tfrac{1}{2},\;\; Y \approx 0{,}3\!-\!1

con σD\sigma_D la volatilidad diaria, QQ el volumen de la metaorden y VDV_D el volumen diario medio —regla mnemotécnica: cuesta aproximadamente un día de volatilidad operar un día de volumen (🦜️🔗 LangChain). El impacto es cóncavo en QQ: las estrategias de alta rotación sobre posiciones pequeñas apenas lo sufren, pero cualquier escalado institucional lo convierte en el límite de capacidad de la estrategia. La aritmética agregada es letal para señales diarias de alta rotación:

CRT  ≈  2×(0,5 s+c)⏟≈ 2–10 bps  +  2 Y σDQ/VD⏟impacto  +  rb/360⏟cortos/dıˊaC_{RT} \;\approx\; \underbrace{2\times(0{,}5\,s + c)}_{\text{≈ 2–10 bps}} \;+\; \underbrace{2\,Y\,\sigma_D\sqrt{Q/V_D}}_{\text{impacto}} \;+\; \underbrace{r_b/360}_{\text{cortos/día}}

Con rotación diaria completa, incluso 5 bps por lado suponen ~25 puntos porcentuales de drag anual (250×2×5 bps250 \times 2 \times 5\,\text{bps}): un Sharpe bruto de 2,0 colapsa por debajo de 0,5. El gráfico siguiente ilustra el efecto sobre una señal de sentimiento sintética: la versión bruta muestra un Sharpe de 1,44; la neta de 6 bps por lado con rotación del 35 % diario cae a 0,67.

Efecto de los costes de transacción sobre una señal de sentimiento LLM (datos sintéticos)Señal LLM, bruta (SR 1,44): 0,40 → 1,0, 85 → 1,1, 169 → 1,1, 252 → 1,1, 336 → 1,3, 419 → 1,4, 499 → 1,5. Misma señal, neta de 6 bps/lado (SR 0,67): 0,40 → 1,0, 85 → 1,0, 170 → 1,0, 255 → 1,0, 340 → 1,1, 424 → 1,1, 499 → 1,2. Benchmark buy-and-hold (ilustrativo): 0,40 → 1,0, 87 → 1,0, 170 → 0,9, 254 → 0,8, 337 → 0,8, 420 → 0,8, 499 → 0,9Señal LLM, bruta (SR 1,44)Misma señal, neta de 6 bps/lado (SR 0,67)Benchmark buy-and-hold (ilustrativo)0,80,91,01,11,21,31,41,50100200300400500Días de mercadoValor de 1 USD invertido
Figura 9.1. Sharpe bruto (1,44) frente a neto (0,67) de una señal de sentimiento sintética con 6 bps por lado y rotación diaria del 35%. Elaboración propia con datos sintéticos, jul-2026.
python
# --- Almgren-Chriss: trayectoria óptima de liquidación ---
import numpy as np

def almgren_chriss_trajectory(X, T, N, sigma, eta, lam):
    tau = T / N
    kappa = np.sqrt(lam * sigma**2 / eta)           # urgencia κ ≈ sqrt(λσ²/η)
    t = np.arange(N + 1) * tau
    x = X * np.sinh(kappa * (T - t)) / np.sinh(kappa * T)
    return t, x, -np.diff(x), kappa                 # ventas n_j por intervalo

t, x, n, k = almgren_chriss_trajectory(1e6, T=1.0, N=390,
                                       sigma=0.02, eta=1e-7, lam=1e-6)
print(f"Vida media de la liquidación θ=1/κ = {1/k:.3f} días")

# --- Coste total en bps: spread + impacto sqrt + borrow ---
def coste_total_bps(Q, V_dia, sigma_dia, spread_bps, comision_bps,
                    Y=0.6, r_borrow=0.0, dias_corto=0, direccion=1):
    impacto = Y * sigma_dia * np.sqrt(Q / V_dia) * 1e4
    borrow  = r_borrow / 360 * dias_corto * 1e4 if direccion < 0 else 0.0
    return 0.5 * spread_bps + impacto + comision_bps + borrow

FINSABER aporta la verificación empírica a escala: en backtests sistemáticos de dos décadas y más de 100 símbolos, las ventajas de las estrategias LLM basadas en timing se deterioran significativamente respecto a las ventanas estrechas reportadas, y el análisis por régimen muestra el patrón perverso de exceso de cautela en mercados alcistas y exceso de agresividad en bajistas (arXiv.org). La tabla siguiente condensa el checklist de validación que un comité de asignación institucional exige antes de capital real.

Tabla 9.3 — Checklist institucional de validación de estrategias antes de asignar capital real

#Punto de controlEstándar mínimo
1Costes completosComisiones, spread, slippage calibrado, impacto (√ o Almgren–Chriss), borrow, impuestos (Blockchain News)
2Datos point-in-timeUniversos con delistings; fundamentales con fecha real de publicación (臺灣博碩士論文知識加值系統)
3Validación OOS estrictaWalk-forward anclado/rodante; CPCV con purge + embargo (arXiv.org)
4Corrección de selecciónN trials reportado; DSR > 0,95 o haircut Harvey–Liu; PBO vía CSCV (PyPI)
5Disciplina anti-leakage LLMBacktest solo post-cutoff; tests contrafácticos; baseline aleatorio (arXiv.org)
6Lag de ingesta modeladoTimestamps de disponibilidad, no de publicación (horizontrading.io)
7Paper trading prolongado≥ 3–6 meses; slippage realizado vs. asumido; criterio de paridad pre-registrado (ResearchGate)
8Controles pre-tradeRiskGateway determinista, kill-switch, HITL, audit trail (piso 15c3-5) (skywork.ai)
9CapacidadAUM máximo antes de que el impacto cóncavo se coma el alfa (🦜️🔗 LangChain)
10Reproducibilidad y despliegue gradualCódigo, datos y seeds; paper → micro-capital → escalado con TCA continuo (LangChain)

9.4 De la señal a la ejecución gobernada

9.4.1 Brokers, protocolos y la arquitectura RiskGateway + HITL

En el mundo retail-prosumer, Alpaca ofrece API REST/WebSocket comission-free con entorno paper idéntico al live, buying power 4x intradía / 2x overnight, venta en corto y órdenes avanzadas (bracket, OCO, trailing stop); la práctica estándar de idempotencia es el client_order_id único, que evita duplicar órdenes si el agente reintenta tras un timeout (LangChain). Interactive Brokers es la referencia institucional-retail (acceso global multi-activo, feed público de borrow (CSDN博客) ), y el stack Python vigente es ib_async, sucesor mantenido de ib_insync (archivado) (LangChain):

python
from ib_async import *

ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1)          # 7497 = TWS paper

contract = Stock('TSLA', 'SMART', 'USD')
parent = LimitOrder('BUY', 100, 250.00)
parent.orderId = ib.client.getReqId(); parent.transmit = False
stop = StopOrder('SELL', 100, 240.00)
stop.orderId = ib.client.getReqId(); stop.parentId = parent.orderId; stop.transmit = False
tp = LimitOrder('SELL', 100, 260.00)
tp.orderId = ib.client.getReqId(); tp.parentId = parent.orderId; tp.transmit = True

for o in (parent, stop, tp):
    ib.placeOrder(contract, o)

En el mundo institucional la estrategia no habla con el broker: habla con el OMS (ciclo de vida de la orden, compliance, asignación, auditoría —el system of record), que enruta al EMS (smart order routing, algos VWAP/TWAP/IS, TCA), que ejecuta contra venues vía FIX (langchain.com). FIX 4.2 sigue desplegado masivamente para equities: mensajes tag=value delimitados por SOH, con 35=D NewOrderSingle, 35=8 ExecutionReport, 35=F OrderCancelRequest y 35=G Cancel/Replace, sesión con Logon (35=A) y Heartbeat (35=0), y números de secuencia (tag 34) estrictamente monotónicos —un gap dispara ResendRequest (35=2)— (arXiv.org):

text
8=FIX.4.2|49=CLIENT|56=EXCHANGE|34=1|52=20260729-13:00:00|35=D|11=LLM-7F3A|55=AAPL|54=1|38=500|40=2|44=212.50|10=123|

El piso regulatorio es la SEC Rule 15c3-5 (Market Access Rule): tras el Flash Crash, los broker-dealers deben aplicar controles pre-trade automatizados e indelegables —límites de capital y crédito, tamaño máximo de orden, prevención de órdenes erróneas y, para DMA, monitorización en tiempo real y kill-switch— (skywork.ai). La lección para sistemas con LLM es directa: la regulación asume que el algoritmo fallará y obliga a que el guardrail sea independiente del generador de órdenes. La arquitectura que se enseña en este curso implementa exactamente ese principio: el agente nunca tiene credenciales de trading; tiene una herramienta submit_order envuelta en un RiskGateway determinista (límites en código, nunca en el prompt), un interrupt HITL para órdenes sobre umbral y un kill-switch que nunca pide permiso (🦜️🔗 LangChain).

python
class RiskGateway:
    """Capa de riesgo pre-trade determinista (estilo 15c3-5)."""
    def __init__(self, api, max_notional=25_000, max_daily_loss=5_000, max_orders_min=10):
        self.api, self.max_notional = api, max_notional
        self.max_daily_loss, self.max_orders_min = max_daily_loss, max_orders_min
        self.kill = False                      # kill-switch global

    def check(self, symbol, qty, price, side):
        if self.kill:
            raise RiskBlock("KILL-SWITCH ACTIVO: toda nueva orden bloqueada")
        if qty * price > self.max_notional:
            raise RiskBlock(f"Notional {qty*price:.0f} > límite {self.max_notional}")
        if self.daily_realized_pnl() < -self.max_daily_loss:
            self.kill = True                   # auto-kill por pérdida diaria
            self.cancel_and_flatten()
            raise RiskBlock("Límite de pérdida diaria: posiciones cerradas")
        if self.orders_last_minute() >= self.max_orders_min:
            raise RiskBlock("Rate limit interno excedido (anti runaway-loop)")
        if side == "sell" and not self.is_shortable(symbol):
            raise RiskBlock(f"{symbol} no shortable / borrow no disponible")
        return True

class RiskBlock(Exception): pass

La integración con LangChain usa HumanInTheLoopMiddleware: interrumpe la ejecución en llamadas a herramientas configuradas, persiste el estado con un checkpointer y admite decisiones approve/edit/reject/respond, con interrupts condicionales (when) para exigir aprobación humana solo por encima de un notional dado (🦜️🔗 LangChain):

python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, ToolCallRequest

def big_order(request: ToolCallRequest) -> bool:
    a = request.tool_call["args"]
    return a.get("qty", 0) * a.get("limit_price", 0) > 10_000   # HITL condicional

agent = create_agent(
    model="gpt-5.5",
    tools=[get_market_data, compute_signal, submit_order, cancel_all_orders],
    middleware=[HumanInTheLoopMiddleware(
        interrupt_on={
            "submit_order": {"allowed_decisions": ["approve", "reject"], "when": big_order},
            "cancel_all_orders": False,      # el kill-switch NUNCA pide permiso
            "get_market_data": False,
        },
        description_prefix="ORDEN PENDIENTE DE APROBACIÓN",
    )],
    checkpointer=AsyncPostgresSaver(...),      # obligatorio para HITL
)
El RiskGateway bloquea las órdenes del agente, las escala a aprobación humana o las envía al broker; el kill-switch cancela todo y el audit log lo registra.Señal validada DSR>0.95, costes, post-cutoff → Agente LLM LangChain propone orden + tesis; Agente LLM LangChain propone orden + tesis → submit_order tool; submit_order tool → RiskGateway determinista notional · pérdida diaria auto-kill rate limit · borrow · whitelist; RiskGateway determinista notional · pérdida diaria auto-kill rate limit · borrow · whitelist → RiskBlock → audit log (bloquea); RiskGateway determinista notional · pérdida diaria auto-kill rate limit · borrow · whitelist → HITL interrupt approve / reject checkpoint Postgres (notional > umbral); RiskGateway determinista notional · pérdida diaria auto-kill rate limit · borrow · whitelist → Broker API paper por defecto → live con promoción explícita (dentro de límites); HITL interrupt approve / reject checkpoint Postgres → Broker API paper por defecto → live con promoción explícita (approve); HITL interrupt approve / reject checkpoint Postgres → RiskBlock → audit log (reject); Broker API paper por defecto → live con promoción explícita → Alpaca / IBKR ib_async FIX→OMS→EMS; Kill-switch botón operador + auto-triggers: pérdida, slippage anómalo, datos corruptos → Alpaca / IBKR ib_async FIX→OMS→EMS (cancela todo y aplana); Broker API paper por defecto → live con promoción explícita → Audit log inmutable client_order_id idempotentebloqueanotional > umbraldentro de límitesapproverejectcancela todo y aplanaSeñal validadaDSR>0.95, costes, post-cutoffAgente LLM LangChainpropone orden + tesissubmit_order toolRiskGateway deterministanotional · pérdida diaria auto-killrate limit · borrow · whitelistRiskBlock → audit logHITL interruptapprove / rejectcheckpoint PostgresBroker APIpaper por defecto → livecon promoción explícitaAlpaca / IBKR ib_asyncFIX→OMS→EMSKill-switchbotón operador + auto-triggers:pérdida, slippage anómalo, datos corruptosAudit log inmutableclient_order_id idempotente
Ejecución con RiskGateway y kill-switch. Los límites viven en código: el RiskGateway bloquea, envía a HITL o deja pasar, y el kill-switch cancela todo y aplana sin pedir permiso.

Los disparadores del kill-switch son concretos y monitorizables: pérdida diaria, desviación del slippage realizado frente al asumido en el backtest, latencia anómala, errores de datos (gaps, timestamps futuros), halts LULD, o el botón manual del operador. El equivalente FIX es un 35=F masivo seguido de cierre de sesión (35=5). La métrica honesta de cierre del módulo es, por tanto, única: el Sharpe neto de costes, deflactado por los N trials, validado post-cutoff y ejecutado bajo controles estilo 15c3-5. Todo lo demás es marketing del backtest.

Ejercicios

  1. Backtest de señal de sentimiento con vectorbt. Construya una señal sintética de scores LLM sobre 50 tickers (o descargue titulares reales y puntúelos con un modelo vía batch API), agréguela con decay de vida media 2 días y conviértala en entradas/salidas long-only del quintil superior. Ejecute Portfolio.from_signals con fees=0, slippage=0 y con fees=0.001, slippage=0.001; reporte Sharpe bruto, neto y turnover implícito. Repita con fees=0.003 y discuta a qué nivel de fricción la señal deja de ser viable.
  2. Cálculo del DSR. Usando las funciones probabilistic_sharpe, expected_max_sharpe y deflated_sharpe del módulo, tome el Sharpe neto del ejercicio 1 y calcule el DSR asumiendo N ∈ {1, 10, 100, 1.000} trials, con la asimetría y curtosis muestrales de sus retornos. ¿A partir de qué N el DSR cae por debajo de 0,95? Compare con el haircut de Harvey–Liu para 100 tests.
  3. Simulación de costes Almgren–Chriss. Liquide 1.000.000 de acciones en un día (390 intervalos de 1 minuto) con σ = 0,02, η = 10⁻⁷ y λ ∈ {10⁻⁷, 10⁻⁶, 10⁻⁵}. Grafique las tres trayectorias xjx_j, calcule la vida media θ = 1/κ de cada una y verifique numéricamente el límite TWAP cuando κT ≪ 1. Estime después el coste de impacto con la square-root law para Q/V_D ∈ {1 %, 5 %, 25 %} y Y = 0,6.
  4. Aritmética de rotación. Una estrategia de sentimiento rota el 100 % de la cartera diariamente, mitad en cortos sobre small-caps con borrow del 12 % anual. Calcule el drag anual total con 8 bps por lado de fricción (spread + comisión) y el borrow diario de la pierna corta. ¿Qué Sharpe bruto mínimo necesita la señal para un Sharpe neto de 1,0, asumiendo volatilidad anual del 15 %?
  5. Diseño de un RiskGateway. Extienda la clase RiskGateway del módulo con: (a) límite de concentración por sector; (b) límite de participación de volumen (≤ 10 % del ADV del símbolo, estimado con la square-root law); (c) lista blanca de símbolos; (d) auto-kill si el slippage medio realizado de las últimas 50 órdenes supera en 2× al asumido en el backtest. Escriba los tests unitarios que demuestren que ninguna orden puede alcanzar el broker saltándose un límite, incluido el caso de un agente en bucle que emite 200 órdenes por minuto.
  6. Auditoría de un paper. Elija uno de los papers de la tabla de la sección 9.1.1 y evalúelo contra los 10 puntos del checklist institucional: identifique qué puntos satisface, cuáles viola y qué experimento adicional mínimo sería necesario para que sus resultados fueran admisibles ante un comité de asignación. Presente sus conclusiones en una página con el formato evidencia → regla → conclusión.

Módulo 9 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. TradingAgents reporta un Sharpe de 8,21 en AAPL sobre una ventana de tres meses de 2024. ¿Cuál es la lectura profesional correcta?

    Ver respuesta

    B. El paper incluye un pie autocrítico: el Sharpe excede el rango empírico (SR > 3 ya es «excelente») y lo atribuyen a la ausencia de correcciones en la ventana. Con 11 llamadas LLM y más de 20 llamadas a herramientas por predicción, solo pudieron testear tres meses. Es un patrón arquitectónico valioso, no evidencia de alfa.

  2. En Lopez-Lira & Tang, la pierna corta aporta 29 bps/día frente a 9 bps/día de la larga. ¿Qué implicación práctica tiene esta asimetría?

    Ver respuesta

    B. La señal gana sobre todo acertando malas noticias en small-caps, donde los límites al arbitraje encarecen la corrección. Eso significa cortos de alta rotación sobre valores potencialmente hard to borrow (10–100 % anual), mientras un ETF general collateral cotiza al 0,7 %. El backtest sin borrow no es conservador: es incompleto en su componente central.

  3. ¿Por qué el pipeline de señal del módulo usa mediana y MAD (en vez de media y desviación típica) y luego neutraliza frente a sector?

    Ver respuesta

    C. Mediana y 1,4826×MAD son estadísticos robustos: un titular extremo no arrastra la normalización de todo el cross-section. La neutralización (regresión sobre dummies sectoriales y exposiciones, quedándose con el residual) evita que la señal sea en realidad una apuesta a que el sector energético sube: premia al valor positivo contra su propio sector.

  4. Una señal ha sobrevivido a los barridos paramétricos en vectorbt. ¿Cuál es el siguiente paso correcto antes de asignar capital real?

    Ver respuesta

    D. El patrón profesional es un pipeline de dos motores: el vectorizado sirve para descubrimiento masivo, pero sus fills al cierre de barra son una aproximación de descubrimiento, no una simulación de ejecución. Nunca se despliega desde el motor vectorizado; la paridad de código de nautilus_trader/LEAN elimina además los errores de traducción research→producción.

  5. Un Sharpe de 1,2 anual (3 años diarios, skew −1, kurt 6) da un PSR clásico de 0,977, pero un DSR de 0,747 admitiendo N=100 trials. ¿Qué conclusión es correcta?

    Ver respuesta

    B. El DSR sustituye el benchmark cero por el máximo esperado bajo la nula de N estrategias sin skill. No demuestra que el resultado sea falso: dice que, dada la exploración realizada, no es estadísticamente separable del mejor de 100 sorteos. Además, N cuenta las decisiones de diseño mirando datos, no solo los backtests guardados.

  6. En Almgren–Chriss, ¿qué trayectoria de liquidación resulta cuando κT ≪ 1?

    Ver respuesta

    B. κ ≈ √(λσ²/η) mide la urgencia: con aversión al riesgo o volatilidad bajas frente al coste de impacto, el riesgo temporal pesa poco y lo óptimo es minimizar impacto repartiendo como un TWAP. El límite opuesto (κT ≫ 1) es la liquidación exponencial anticipada. El ejercicio 3 del módulo pide verificarlo numéricamente.

  7. En la arquitectura del módulo, ¿por qué la herramienta cancel_all_orders se configura con interrupt_on: False, fuera del HITL?

    Ver respuesta

    C. La Market Access Rule nació tras el Flash Crash y obliga a controles pre-trade automatizados e indelegables, con kill-switch para acceso DMA. Un kill-switch que requiere aprobación humana no es un kill-switch: el guardrail debe ser determinista e independiente del sistema estocástico que genera las órdenes, como el auto-kill por pérdida diaria del RiskGateway.

  8. El agente reintenta una orden tras un timeout de red. ¿Qué mecanismo del módulo evita que la orden se duplique en el broker?

    Ver respuesta

    B. La idempotencia se resuelve en el broker con un identificador de orden de cliente único: si el reintento llega con el mismo client_order_id, el broker no crea una segunda orden. El tag 34 de FIX ordena la sesión y detecta gaps (ResendRequest 35=2), pero no deduplica órdenes nuevas; el checkpointer persiste estado del agente, no del broker.