➜ ~ cat blog/inversor-ia.md

Inversor-IA: anatomía de un analista de inversiones con LLM que no confía en sí mismo

Cómo construí en 3 días un sistema de tesis de inversión con Python y Claude, y por qué el 80% de la ingeniería está en desconfiar del modelo.

3días de construcción
12commits
111tests
~15comandos de CLI
Índice del artículo
  1. El problema y la restricción de diseño
  2. Stack: aburrido a propósito
  3. El motor de señales
  4. El prompt propone, el validador dispone
  5. Scorecard y sesgo retrospectivo
  6. El resto del sistema, en rápido
  7. Números y moraleja

##El problema y la restricción de diseño

Quería un sistema que analizara mi portafolio una vez al mes y me entregara razonamiento auditable — no predicciones. Esa restricción, escrita en la spec antes que ninguna línea de código, definió toda la arquitectura: el sistema evalúa condiciones actuales y está estructuralmente incapacitado para prometer futuros. Sin precios objetivo, sin "esta acción subirá", sin conexión a brokers.

La unidad central es la tesis razonada, con un pipeline obligatorio:

datos → señales → argumentos a favor Y en contra → qué invalidaría la tesis → postura

La postura sale de un menú cerrado (AUMENTAR|MANTENER|REDUCIR|SALIR|VIGILAR para posiciones; ENTRAR|ESPERAR para candidatos). Y la regla de oro: sin argumentos en contra, la tesis no se emite. Es un control de sesgo de confirmación convertido en requisito estructural.

##Stack: aburrido a propósito

Python 3.12, SQLite, yfinance, la API de Anthropic (Claude Sonnet) y rich para la CLI. Cero frameworks. Cuatro tablas. La decisión menos glamorosa y más rentable: una capa de proveedor de datos con interfaz común (ProveedorDatos), de modo que yfinance es reemplazable por EODHD (ya integrado para histórico) sin tocar señales ni tesis, y una caché diaria en SQLite con patrón decorador: la clave incluye la fecha (AAPL:historico:2026-07-16), así que expira sola a medianoche.

Un detalle de la caché que solo aparece operando: los resultados vacíos no se cachean. yfinance, ante un fallo de red, no lanza excepción — devuelve vacío. Si cacheas ese vacío, un tropiezo matinal se convierte en un día entero sin datos.

##El motor de señales: matemática que no se negocia

RSI(14) con suavizado de Wilder, SMA50/200, MACD(12,26,9), volatilidad anualizada, distancia a extremos de 52 semanas. Cada indicador devuelve valor numérico y lectura en lenguaje humano ("RSI 74 → sobrecompra"), porque el sistema es también una herramienta de aprendizaje.

La verificación fue doble: el dataset clásico de Wilder (el RSI debe dar exactamente los valores publicados: 70.53, 57.97) y una implementación independiente con pandas como referencia — coincidencia a 4 decimales sobre datos reales. Los bordes importan más de lo que parece: un solo cierre en 0 (dato sucio real de Yahoo) rompía el logaritmo de la volatilidad; una serie plana producía "RSI 100 → sobrecompra" sin que nada hubiera subido. Cada borde terminó como test.

##La lección central: el prompt propone, el validador dispone

El generador arma un contexto JSON por ticker (señales calculadas, fundamentales, titulares del mes, catalizadores, la posición y la tesis del mes anterior) y llama al modelo con un prompt versionado en el repo que solo cambia con aprobación humana explícita. Hasta ahí, lo fácil.

Lo difícil: la respuesta no se acepta — se valida estructuralmente. Mi primera versión buscaba subcadenas ("¿aparece 'argumentos en contra'?") y un panel de agentes revisores la destrozó con contraejemplos ejecutables: una tesis que enumera las secciones en un párrafo introductorio validaba sin tenerlas; "no hay razones para SALIR; lo sensato es MANTENER" registraba la postura opuesta al texto. La versión final parsea las secciones reales del documento (encabezados), exige contenido mínimo en cada una y una postura única e inequívoca dentro de su sección. Si el modelo menciona dos opciones del menú, se rechaza y se reintenta explicándole el motivo exacto. Tras N intentos, el sistema prefiere fallar ruidosamente antes que archivar una tesis coja.

Otro clásico de producción: la primera tesis real llegó cortada por max_tokens y el validador la rechazó tres veces por "faltar" justo las secciones finales. La corrección no fue solo subir el límite: fue detectar stop_reason == "max_tokens" y tratarlo como problema distinto, porque reintentarle a un modelo con el mismo techo es tirar dinero.

##El sistema que se audita: scorecard y sesgo retrospectivo

Cada revisión mensual abre auditando las tesis del mes anterior. Un segundo prompt (también versionado, también con gate humano) dictamina SOSTENIDA|INVALIDADA con una regla que me importa más que cualquier métrica: si una condición de invalidación declarada ocurrió, la tesis está invalidada aunque el precio haya acompañado. Se audita el razonamiento, no la suerte.

Y la decisión de diseño más contraintuitiva: me negué a generar tesis retroactivas para fabricar historial. Podía reconstruir los precios de mayo, pero no los fundamentales point-in-time ni las noticias de entonces — y el modelo ya sabe el final de la película. Eso tiene nombre: sesgo retrospectivo (look-ahead bias). Un historial corto y limpio vale más que uno largo y contaminado.

Entre revisiones, un comando de vigilancia compara las señales archivadas en el contexto_json de cada tesis (auditoría por diseño: toda tesis guarda los datos con los que se razonó) contra las de hoy, y reporta cambios de régimen objetivos — cruce de medias invertido, pérdida de la SMA50, RSI en zona extrema — junto al texto de invalidación que la tesis declaró. Deliberadamente no interpreta ese texto con IA: detecta hechos y deja el juicio al humano.

##El resto del sistema, en rápido

  • Dashboard: un HTML autocontenido generado por Python — sparklines SVG dibujados a mano (sin librerías), CSS inline, cero peticiones salientes. El markdown de cada tesis se re-renderiza escapando todo primero (el texto viene de un LLM: nada de HTML crudo en mi página) y rebajando su jerarquía de encabezados para no romper el documento.
  • Explorador: screener sobre un universo curado (~90 tickers por sectores) filtrado por un perfil.json editable. Reusa el motor de señales verificado y produce un puntaje con motivos legibles — nada de cajas negras. Su primer hallazgo real: una large-cap en sobreventa profunda tras un desplome por earnings, detectada por pura matemática.
  • Operación: LaunchAgent mensual (día 1, 9:00) que corre revisión + dashboard + respaldo. Los datos personales (base SQLite, reportes, dashboard) viven fuera de git; el código, en un repo privado.

##Números y moraleja

3 días, 12 commits, 111 tests, ~15 comandos de CLI. Construido en pareja con Claude Code, hito por hito, con revisión humana en cada gate y paneles de agentes revisores que encontraron bugs reales ejecutando el código (el mejor: la caché que "envenenaba" el día entero con un fallo transitorio).

Si me llevo una sola idea: con LLMs, la calidad de un sistema no vive en el prompt — vive en todo lo que construyes alrededor para no tener que confiar en él. Validadores estructurales, menús cerrados, datos citables, auditoría posterior y la humildad de declarar qué invalidaría tu propia conclusión. Que es, curiosamente, lo mismo que le pido ahora a mis inversiones.