Lección 0.2.6 · 40 min
Introducción a los asistentes de código con IA: prompts, revisión y tests
Un asistente escribe en segundos una señal de trading que parece correcta. Esta lección sigue un caso completo: la función que propone, el backtest espectacular que produce, el test que demuestra que usa datos del futuro y el prompt que lo habría evitado.
- Módulo
- 0.2 · Entorno de trabajo moderno
- Duración
- 40 min
Lecciones del nivel 0
0.1 · Bienvenida y expectativas
0.2 · Entorno de trabajo moderno
- 0.2.1 Instalación de Python con uv: versiones y ciclo de soporte
- 0.2.2 Entornos virtuales y dependencias: uv init, uv add, pyproject.toml y lockfiles
- 0.2.3 Editores (VS Code, Cursor, PyCharm) y notebooks (Jupyter, JupyterLab, marimo)
- 0.2.4 Terminal básica, rutas, variables de entorno y ficheros .env
- 0.2.5 Git y GitHub: commits, ramas, pull requests y .gitignore
- 0.2.6 Introducción a los asistentes de código con IA: prompts, revisión y tests
0.3 · Fundamentos del lenguaje
- 0.3.1 Variables, tipos y operadores; coma flotante y decimal para precios y ticks
- 0.3.2 Cadenas, f-strings y formato de números y fechas
- 0.3.3 Listas, tuplas, diccionarios, conjuntos y comprensiones
- 0.3.4 Control de flujo: if, elif, else, for, while y match-case
- 0.3.5 Funciones: argumentos, *args y **kwargs, lambdas y ámbito
- 0.3.6 Excepciones; módulos y paquetes, con los imports perezosos de Python 3.15
0.4 · Python intermedio
- 0.4.1 Programación orientada a objetos: clases, herencia, composición y dataclasses
- 0.4.2 Tipado: type hints, mypy o ty y Pydantic para configuraciones
- 0.4.3 Iteradores, generadores, decoradores y context managers
- 0.4.4 Ficheros (CSV, JSON, YAML, TOML) y fechas con zonas horarias
- 0.4.5 Logging profesional y testing con pytest
- 0.4.6 Concurrencia: threading, multiprocessing, asyncio y free-threading
0.5 · Computación numérica y datos
- 0.5.1 NumPy 2: arrays, broadcasting, vectorización, álgebra lineal y generadores aleatorios
- 0.5.2 pandas 3.0: Series, DataFrame, índices temporales, Copy-on-Write, tipo str y pd.col()
- 0.5.3 Selección, filtrado, groupby, merge, pivot, resample y ventanas móviles
- 0.5.4 Polars: API lazy frente a eager, expresiones, rendimiento y cuándo elegirlo frente a pandas
- 0.5.5 DuckDB y SQL esencial: SELECT, JOIN, GROUP BY y funciones de ventana sobre Parquet
0.6 · Visualización
0.7 · Matemáticas y estadística mínimas
Qué es un asistente de código
Un asistente de código es un modelo de lenguaje entrenado con enormes cantidades de código y texto que propone código a partir de una descripción y del contexto que le des. Hay tres formas de usarlo, que se diferencian en cuánto hace por su cuenta:
| Forma | Qué hace | Ejemplos | Qué revisar |
|---|---|---|---|
| Autocompletado | Sugiere la línea o la función que estás escribiendo | Copilot en VS Code, Cursor | Cada sugerencia antes de aceptarla |
| Chat | Responde preguntas y escribe fragmentos que copias tú | Chat del editor, Claude, ChatGPT | El fragmento entero y cómo encaja en tu código |
| Agente | Lee el repositorio, edita ficheros y ejecuta comandos y tests | Claude Code, Codex, el agente de Cursor | El diff completo, como una pull request (lección 0.2.5) |
Las tres comparten una propiedad: el código que producen es plausible. Tiene el aspecto del código que resolvería el problema, con nombres razonables y comentarios convincentes. Muchas veces es también correcto. Pero el modelo no conoce tus datos, no sabe a qué hora se opera tu señal y no ha ejecutado tu backtest. Lo que no le digas, lo decide él sin avisar.
En trading esto tiene un coste concreto. Un error de programación normal suele romper algo: una excepción, un gráfico vacío. Un error de anticipación (lección 0.1.1) no rompe nada: produce un backtest mejor. Es el error que más cuesta ver y el que más dinero hace perder, porque convence.
La lección 0.1.3 fijó tres reglas para usar asistentes. Siguen en pie y esta lección explica el porqué de la segunda:
- Nivel 0 sin asistente. El código de los ejercicios lo escribes tú. Puedes preguntar dudas conceptuales a un chat. Esta lección no cambia eso: enseña a juzgar código generado, que es justo lo que no se puede hacer sin saber escribirlo.
- Después, la IA propone y tú decides: primero la prueba, después el código, lectura completa, pruebas y commit.
- Nada sensible en el prompt: ni claves, ni datos con licencia restrictiva, ni datos personales.
El resto de la lección es un caso completo, de los que se repiten a diario.
Un caso: la señal que propone el asistente
Un usuario pide a un chat, sin más contexto:
Escribe una función de pandas que dé una señal de compra cuando la media móvil de 20 sesiones del precio de cierre tenga pendiente positiva. Quiero una media suavizada y sin retraso.
Los precios son los de SINT, el ETF ficticio del curso:
import pandas as pd
precios = pd.read_csv("sint_diario.csv", parse_dates=["fecha"],
index_col="fecha")
cierre = precios["cierre"]
rent = cierre.pct_change().fillna(0)
print(len(cierre), "sesiones;", cierre.index[0].date(), "a",
cierre.index[-1].date())1303 sesiones; 2021-01-04 a 2025-12-31La respuesta, con el aspecto típico de un asistente (docstring, nombres claros, un comentario que justifica la decisión):
def senal_pendiente_ia(cierre: pd.Series,
ventana: int = 20) -> pd.Series:
"""1 si la media móvil sube, 0 si no.
Se usa una media centrada para eliminar el retraso."""
media = cierre.rolling(ventana, center=True).mean()
return (media.diff() > 0).astype(int)El usuario hace lo que aprendió en la lección 0.1.1: desplaza la posición un día para operar después de la señal, y mide el resultado frente a comprar y mantener:
def backtest(senal: pd.Series) -> pd.Series:
"""Rentabilidad diaria; la señal del cierre se opera al día
siguiente."""
posicion = senal.shift(1).fillna(0)
return posicion * rent
def resumen(r: pd.Series) -> str:
total = (1 + r).prod() - 1
sharpe = r.mean() / r.std() * 252 ** 0.5
return f"total {total:+7.1%} Sharpe {sharpe:5.2f}"
r_ia = backtest(senal_pendiente_ia(cierre))
print(f"{'Asistente':<20}", resumen(r_ia))
print(f"{'Comprar y mantener':<20}", resumen(rent))Asistente total +324.1% Sharpe 2.10
Comprar y mantener total +43.5% Sharpe 0.48Un Sharpe por encima de 2 con una media móvil, en un activo que en el mismo periodo se queda en 0,48. La curva de la cabecera (en ámbar) sube casi sin retrocesos. Es el momento de sospechar: una regla tan simple no debería batir así al mercado, y menos en un activo con una caída del 25 % en 2022.
La línea resaltada es el problema: center=True. Por defecto, rolling etiqueta cada ventana con su último día:
la media del 3 de junio usa los 20 cierres que terminan el 3 de junio. Con center=True, la etiqueta es el centro
de la ventana: la media del 3 de junio usa diez cierres anteriores, el del propio día y nueve posteriores. «Sin retraso», sí, porque mira
al futuro. Se puede comprobar qué días entran en cada ventana sustituyendo los precios por su posición en la serie:
pos = pd.Series(range(len(cierre)), index=cierre.index)
dia = pd.Timestamp("2024-06-03")
for centrada in (False, True):
v = pos.rolling(20, center=centrada)
primero = cierre.index[int(v.min()[dia])].date()
ultimo = cierre.index[int(v.max()[dia])].date()
print(f"center={centrada!s:<5}: de {primero} a {ultimo}")center=False: de 2024-05-07 a 2024-06-03
center=True : de 2024-05-20 a 2024-06-14Con la media centrada, la señal del 3 de junio sabe cómo cerró el mercado hasta el 14 de junio. Desplazar la posición un día no sirve de nada: el futuro ya está dentro de la señal, no en el desfase. Y nada en el código lo delata a simple vista; el comentario incluso lo presenta como una ventaja.
Un test que lo detecta
Leer con atención ayuda, pero no escala: hay decenas de formas de colar el futuro en una señal (center=True,
shift(-1), rellenar huecos hacia atrás con bfill, normalizar con la media de toda la muestra…). Una prueba que no
depende de la forma del error es mejor. La idea: la señal de un día no puede cambiar si se añaden datos
posteriores. Si se calcula con los datos hasta una fecha y después con todos, las señales de esas fechas deben
coincidir:
def comprobar_sin_anticipacion(funcion, cierre: pd.Series,
cortes=(250, 500, 750, 1000)):
completa = funcion(cierre)
for n in cortes:
parcial = funcion(cierre.iloc[:n])
distintos = int((parcial != completa.iloc[:n]).sum())
assert distintos == 0, (
f"hasta {cierre.index[n - 1].date()}: {distintos} días "
"de la señal cambian al añadir datos posteriores")Es una función con assert sencillos: si la condición no se cumple, lanza un error con el mensaje. Con pytest
(lección 0.4.5) se convierte en un test que se ejecuta solo antes de cada commit. Aplicada a la función del
asistente:
comprobar_sin_anticipacion(senal_pendiente_ia, cierre)AssertionError: hasta 2021-12-17: 9 días de la señal cambian al añadir datos posterioresNueve señales de diciembre de 2021 cambian cuando se añaden los cierres posteriores al 17 de diciembre. El test no sabe nada de medias centradas: solo comprueba una propiedad que cualquier señal honesta cumple. Por eso sirve también para la próxima función que te proponga un asistente, sea cual sea su error.
La corrección es quitar center=True: la media de cada día usa ese día y los 19 anteriores. Tiene retraso, y ese
retraso es el precio de no conocer el futuro. La versión corregida pasa el test:
def senal_pendiente(cierre: pd.Series,
ventana: int = 20) -> pd.Series:
"""1 si la media móvil (solo datos pasados) sube; 0 si no."""
media = cierre.rolling(ventana).mean()
return (media.diff() > 0).astype(int)
comprobar_sin_anticipacion(senal_pendiente, cierre)
print("Sin anticipación")
r_ok = backtest(senal_pendiente(cierre))
print(f"{'Corregida':<20}", resumen(r_ok))Sin anticipación
Corregida total -8.9% Sharpe -0.07De +324 % a −8,9 %. La estrategia real no bate a comprar y mantener; la del asistente no existía. Las tres curvas de la figura de la cabecera salen de estas celdas. Fíjate en que la versión corregida es peor: un test bien hecho a menudo empeora el resultado, porque elimina una ventaja falsa. Si después de corregir un error tu backtest mejora, revisa dos veces.
El prompt que lo habría evitado
El primer prompt dejaba al modelo decidir lo importante. Uno mejor da el contexto, el contrato de los datos, la regla temporal explícita y la prueba que tiene que pasar:
Contexto: backtest diario en pandas 3.0.
cierrees unapd.Seriesde cierres diarios con índice de fechas ordenado y sin huecos.Tarea: función
senal_pendiente(cierre, ventana=20) -> pd.Seriesque devuelva 1 si la media móvil simple deventanasesiones sube respecto al día anterior y 0 si no, con el mismo índice quecierre.Regla temporal: la señal del día t solo puede usar cierres hasta t, incluido. Nada de
center=True,shiftnegativo,bfillni estadísticos de toda la muestra. La posición se desplaza fuera de la función.Debe pasar este test: (pegar
comprobar_sin_anticipacion).Sin dependencias nuevas. Explica en una frase qué hace cada línea.
Esto es lo que la regla 2 del curso pide: la prueba la escribes tú, antes, y el asistente la recibe como parte de la tarea. Si la prueba la escribiera el mismo asistente que la función, podría heredar el mismo malentendido. La última línea, pedir que explique cada línea, no garantiza nada, pero facilita la lectura completa que tienes que hacer igual.
Con un agente: instrucciones y permisos
Un agente no recibe un prompt suelto: trabaja dentro del repositorio, lee ficheros, ejecuta comandos y cambia varios ficheros a la vez. Eso cambia dos cosas.
La primera, el contexto. Lo que en un chat repetirías en cada prompt se escribe una vez en un fichero de instrucciones
del repositorio, que el agente lee al empezar: CLAUDE.md en Claude Code, AGENTS.md en Codex y en muchos
otros1Nota 1 · Claude Code Docs, «Cómo Claude recuerda tu proyecto» (code.claude.com/docs/es/memory), y el formato abierto
AGENTS.md (agents.md), que lista los agentes compatibles; consultados el 21 de octubre de 2026.. Para el proyecto del curso bastan unas líneas:
# Reglas del proyecto ppt-curso
- Python 3.14 con uv. Dependencias solo con `uv add`, nunca pip.
- Señales: el valor del día t usa datos hasta el cierre de t.
La posición es la señal desplazada un día. Prohibidos
center=True, shift negativo, bfill y estadísticos de toda la muestra.
- Toda señal nueva pasa comprobar_sin_anticipacion (tests/).
- Antes de terminar: `uv run pytest`. No cambies un test para
que pase.
- No leas ni muestres `.env`. Ninguna clave en el código.La línea sobre los tests no es un adorno: un agente al que se le pide que «haga pasar los tests» puede conseguirlo cambiando el test en lugar del código. Por eso el diff se revisa entero, tests incluidos.
La segunda, los permisos. Un agente que ejecuta comandos puede leer cualquier fichero al que tú tengas acceso,
incluido .env. Mientras aprendes: claves de papel o ninguna, trabajo en una rama y cambios que entran por pull
request, como los tuyos (lección 0.2.5). Nunca un agente con acceso a una cuenta real del bróker.
Qué revisar en código generado
Al revisar código generado para trading, esta lista cubre los errores más comunes:
| Revisa | Señales de alarma |
|---|---|
| Anticipación | center=True, shift(-n), bfill, medias o desviaciones de toda la muestra, modelos ajustados con todos los datos |
| Desfase | Señal y rentabilidad del mismo día multiplicadas sin shift(1) |
| Costes | Ninguna comisión ni deslizamiento; operaciones diarias sin coste |
| Huecos | fillna(0) en precios, dropna que desalinea series, festivos |
| Dependencias | Paquetes que no conoces: compruébalos en PyPI antes de uv add |
| Secretos | Claves escritas en el código o impresas en logs (lección 0.2.4) |
Ejercicios
- Sin ejecutarla, di si esta señal tiene sesgo de anticipación y por qué; después compruébalo con
comprobar_sin_anticipacion:(cierre > cierre.mean()).astype(int).
Solución
def senal_sobre_media_total(cierre: pd.Series) -> pd.Series:
return (cierre > cierre.mean()).astype(int)
try:
comprobar_sin_anticipacion(senal_sobre_media_total, cierre)
except AssertionError as error:
print("Anticipación:", error)Anticipación: hasta 2021-12-17: 115 días de la señal cambian al añadir datos posterioresSí: cierre.mean() es la media de los cinco años, así que la señal de 2021 depende de los precios de 2025. Es uno de
los errores de la tabla (estadísticos de toda la muestra) y el test lo detecta sin saber nada de él.
- Un asistente rellena los huecos de una serie de precios con
cierre.bfill(). ¿Por qué es anticipación? ¿Con qué lo sustituirías?
Solución
bfill rellena cada hueco con el siguiente valor conocido: el precio de un día festivo o sin datos sería el
cierre de un día posterior. Si la señal usa ese valor, sabe algo del futuro. La alternativa es ffill (el último
valor conocido, que sí existía en ese momento) o, mejor aún, no inventar precios: dejar el hueco y no operar ese día.
La limpieza de huecos se trata en el nivel 1.
- Escribe, sin enviarlo, el prompt que darías a un asistente para una función que calcule la volatilidad móvil de 20
sesiones anualizada a partir de
cierre. Incluye contexto, contrato, regla temporal y al menos una prueba.
Solución
Un ejemplo. Contexto: pandas 3.0, cierre es una pd.Series diaria con índice de fechas ordenado. Tarea:
volatilidad_movil(cierre, ventana=20) -> pd.Series, desviación típica muestral (ddof=1, lección 0.2.2) de las
rentabilidades simples de las últimas ventana sesiones, multiplicada por la raíz de 252, con el mismo índice. Regla
temporal: el valor del día t solo usa cierres hasta t; las primeras sesiones sin ventana completa, NaN. Pruebas: el
test de truncado; con una serie de precios constante, la volatilidad es 0; con una serie que alterna +1 % y −1 %, la
volatilidad es positiva y estable. Sin dependencias nuevas.
Footnotes
-
Claude Code Docs, «Cómo Claude recuerda tu proyecto» (code.claude.com/docs/es/memory), y el formato abierto AGENTS.md (agents.md), que lista los agentes compatibles; consultados el 21 de octubre de 2026. ↩
-
J. Spracklen et al., «We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs» (arXiv:2406.10279), consultado el 21 de octubre de 2026. ↩
Respuesta plegada en cada pregunta
Autoevaluación
¿Por qué
cierre.rolling(20, center=True).mean()introduce sesgo de anticipación en una señal?- APorque
center=Truees más lento - BPorque la media del día t incluye los precios de los días siguientes: el valor de hoy depende de cierres que todavía no existen
- CPorque una ventana de 20 sesiones es demasiado corta
- DNo lo introduce si la posición se desplaza un día
Ver respuesta
Respuesta B. Porque la media del día t incluye los precios de los días siguientes: el valor de hoy depende de cierres que todavía no existen
Con
center=True, la etiqueta de la ventana es su centro: la media del día t usa diez sesiones anteriores y nueve posteriores. Desplazar la posición un día no lo arregla, porque el futuro ya está dentro de la señal.- APorque
¿Qué comprueba el test de truncado de la lección?
- AQue la función es rápida
- BQue la señal calculada con datos hasta una fecha coincide con la calculada con todos los datos para esas mismas fechas
- CQue la señal tiene tantos unos como ceros
- DQue el backtest gana dinero
Ver respuesta
Respuesta B. Que la señal calculada con datos hasta una fecha coincide con la calculada con todos los datos para esas mismas fechas
Si añadir datos futuros cambia la señal de días pasados, la señal usaba información que en su momento no existía. Es una propiedad que cualquier señal debe cumplir, sea cual sea la regla.
Un asistente propone
uv add pandas-ta-propara calcular un indicador. ¿Qué haces antes de instalarlo?- AInstalarlo: si el asistente lo nombra, existe
- BComprobar en PyPI y en la web del proyecto que el paquete existe, quién lo publica y si es el que buscas
- CInstalarlo con
pipen lugar deuv - DPedirle al asistente que confirme que existe
Ver respuesta
Respuesta B. Comprobar en PyPI y en la web del proyecto que el paquete existe, quién lo publica y si es el que buscas
Los modelos inventan nombres de paquetes con frecuencia, y hay atacantes que publican paquetes maliciosos con esos nombres. El asistente no puede comprobarlo por ti: lo compruebas tú en la fuente.
Según las reglas del curso, ¿cuál es el orden correcto a partir del nivel 1?
- APedir el código, ejecutarlo, y si el backtest sale bien, commit
- BEscribir la prueba, pedir el código, leerlo entero, ejecutar las pruebas y solo entonces commit
- CPedir el código y las pruebas al asistente a la vez y hacer commit
- DEscribir el código a mano y pedir al asistente que lo apruebe
Ver respuesta
Respuesta B. Escribir la prueba, pedir el código, leerlo entero, ejecutar las pruebas y solo entonces commit
La prueba, escrita por ti antes, fija lo que el código tiene que cumplir. Si la escribe el mismo asistente que el código, puede heredar el mismo error. Un backtest que «sale bien» no es una prueba: el de la lección salía espectacular.
Ideas clave
- Autocompletado, chat y agente: cuanto más actúa el asistente por su cuenta, más contexto necesita y más hay que revisar.
- El código generado es plausible, no necesariamente correcto. En trading, el error típico es usar datos del futuro sin que se note.
- Un backtest espectacular es un motivo para sospechar, no para hacer commit.
- El test de truncado (la señal hasta t no cambia al añadir datos posteriores) detecta la anticipación en cualquier señal.
- Un buen prompt da el contrato de datos, la regla temporal y los tests que el código debe pasar. En el nivel 0, el código lo escribes tú.